diff --git a/_locales/en/messages.json b/_locales/en/messages.json
index d5bd06d78..f80e651b9 100644
--- a/_locales/en/messages.json
+++ b/_locales/en/messages.json
@@ -110,13 +110,14 @@
"continue": "Continue",
"error": "Error",
"delete": "Delete",
- "deletePublicWarning": "Are you sure? This will permanently remove this message for everyone in this open group.",
- "deleteMultiplePublicWarning": "Are you sure? This will permanently remove these messages for everyone in this open group.",
- "deleteWarning": "Are you sure? Clicking 'delete' will permanently remove this message from this device only.",
- "deleteMultipleWarning": "Are you sure? Clicking 'delete' will permanently remove these messages from this device only.",
"messageDeletionForbidden": "You don’t have permission to delete others’ messages",
- "deleteThisMessage": "Delete message",
+ "deleteJustForMe": "Delete just for me",
+ "deleteForEveryone": "Delete for everyone",
+ "deleteMessagesQuestion": "Delete those messages?",
+ "deleteMessageQuestion": "Delete this message?",
+ "deleteMessages": "Delete Messages",
"deleted": "Deleted",
+ "messageDeletedPlaceholder": "This message has been deleted",
"from": "From:",
"to": "To:",
"sent": "Sent",
@@ -125,14 +126,6 @@
"groupMembers": "Group members",
"moreInformation": "More information",
"resend": "Resend",
- "deleteMessage": "Delete Message",
- "deleteMessageQuestion": "Delete message?",
- "deleteMessagesQuestion": "Delete messages?",
- "deleteMessages": "Delete Messages",
- "deleteMessageForEveryone": "Delete Message For Everyone",
- "deleteMessageForEveryoneLowercase": "Delete Message For Everyone",
- "deleteMessagesForEveryone": "Delete Messages For Everyone",
- "deleteForEveryone": "Delete for Everyone",
"deleteConversationConfirmation": "Permanently delete the messages in this conversation?",
"clearAllData": "Clear All Data",
"deleteAccountWarning": "This will permanently delete your messages, and contacts.",
@@ -440,10 +433,6 @@
"recoveryPhraseRevealMessage": "Secure your account by saving your recovery phrase. Reveal your recovery phrase then store it safely to secure it.",
"recoveryPhraseRevealButtonText": "Reveal Recovery Phrase",
"notificationSubtitle": "Notifications - $setting$",
- "deletionTypeTitle": "Deletion Type",
- "deleteJustForMe": "Delete just for me",
- "messageDeletedPlaceholder": "This message has been deleted",
- "messageDeleted": "Message deleted",
"surveyTitle": "Take our Session Survey",
"goToOurSurvey": "Go to our survey",
"incomingCall": "Incoming call",
diff --git a/stylesheets/_session_conversation.scss b/stylesheets/_session_conversation.scss
index 141fc5b95..c920c44b4 100644
--- a/stylesheets/_session_conversation.scss
+++ b/stylesheets/_session_conversation.scss
@@ -78,7 +78,7 @@
}
.message-selection-overlay .button-group {
- float: right;
+ display: flex;
}
}
diff --git a/ts/components/ConversationListItem.tsx b/ts/components/ConversationListItem.tsx
index e94606cef..58c7401ae 100644
--- a/ts/components/ConversationListItem.tsx
+++ b/ts/components/ConversationListItem.tsx
@@ -86,7 +86,7 @@ const HeaderItem = (props: {
const pinIcon =
isMessagesSection && isPinned ? (
-
+
) : null;
const NotificationSettingIcon = () => {
@@ -99,11 +99,11 @@ const HeaderItem = (props: {
return null;
case 'disabled':
return (
-
+
);
case 'mentions_only':
return (
-
+
);
default:
return null;
diff --git a/ts/components/conversation/ConversationHeader.tsx b/ts/components/conversation/ConversationHeader.tsx
index d8ec8287e..2b0a64f56 100644
--- a/ts/components/conversation/ConversationHeader.tsx
+++ b/ts/components/conversation/ConversationHeader.tsx
@@ -15,6 +15,8 @@ import {
getConversationHeaderTitleProps,
getCurrentNotificationSettingText,
getSelectedConversation,
+ getSelectedConversationIsPublic,
+ getSelectedConversationKey,
getSelectedMessageIds,
isMessageDetailView,
isMessageSelectionMode,
@@ -23,7 +25,10 @@ import {
import { useDispatch, useSelector } from 'react-redux';
import { useMembersAvatars } from '../../hooks/useMembersAvatar';
-import { deleteMessagesById } from '../../interactions/conversationInteractions';
+import {
+ deleteMessagesById,
+ deleteMessagesByIdForEveryone,
+} from '../../interactions/conversations/unsendingInteractions';
import {
closeMessageDetailsView,
closeRightPanel,
@@ -67,16 +72,32 @@ export type ConversationHeaderProps = {
left: boolean;
};
-const SelectionOverlay = (props: {
- onDeleteSelectedMessages: () => void;
- onCloseOverlay: () => void;
- isPublic: boolean;
-}) => {
- const { onDeleteSelectedMessages, onCloseOverlay, isPublic } = props;
+const SelectionOverlay = () => {
+ const selectedMessageIds = useSelector(getSelectedMessageIds);
+ const selectedConversationKey = useSelector(getSelectedConversationKey);
+ const isPublic = useSelector(getSelectedConversationIsPublic);
+ const dispatch = useDispatch();
+
const { i18n } = window;
- const isServerDeletable = isPublic;
- const deleteMessageButtonText = i18n(isServerDeletable ? 'deleteForEveryone' : 'delete');
+ function onCloseOverlay() {
+ dispatch(resetSelectedMessageIds());
+ }
+
+ function onDeleteSelectedMessages() {
+ if (selectedConversationKey) {
+ void deleteMessagesById(selectedMessageIds, selectedConversationKey);
+ }
+ }
+ function onDeleteSelectedMessagesForEveryone() {
+ if (selectedConversationKey) {
+ void deleteMessagesByIdForEveryone(selectedMessageIds, selectedConversationKey);
+ }
+ }
+
+ const isOnlyServerDeletable = isPublic;
+ const deleteMessageButtonText = i18n('delete');
+ const deleteForEveroneMessageButtonText = i18n('deleteForEveryone');
return (
@@ -85,11 +106,19 @@ const SelectionOverlay = (props: {
+ {!isOnlyServerDeletable && (
+
+ )}
@@ -285,7 +314,6 @@ export const ConversationHeaderWithDetails = () => {
const headerProps = useSelector(getConversationHeaderProps);
const isSelectionMode = useSelector(isMessageSelectionMode);
- const selectedMessageIds = useSelector(getSelectedMessageIds);
const selectedConversation = useSelector(getSelectedConversation);
const memberDetails = useMembersAvatars(selectedConversation);
const isMessageDetailOpened = useSelector(isMessageDetailView);
@@ -366,15 +394,7 @@ export const ConversationHeaderWithDetails = () => {
/>
- {isSelectionMode && (
- dispatch(resetSelectedMessageIds())}
- onDeleteSelectedMessages={() => {
- void deleteMessagesById(selectedMessageIds, conversationKey, true);
- }}
- />
- )}
+ {isSelectionMode && }
);
};
diff --git a/ts/components/conversation/MessageDetail.tsx b/ts/components/conversation/MessageDetail.tsx
index 183c4208c..15e94b201 100644
--- a/ts/components/conversation/MessageDetail.tsx
+++ b/ts/components/conversation/MessageDetail.tsx
@@ -5,13 +5,13 @@ import moment from 'moment';
import { Avatar, AvatarSize } from '../Avatar';
import { ContactName } from './ContactName';
import { Message } from './Message';
-import { deleteMessagesById } from '../../interactions/conversationInteractions';
import { useSelector } from 'react-redux';
import { ContactPropsMessageDetail } from '../../state/ducks/conversations';
import {
getMessageDetailsViewProps,
getMessageIsDeletable,
} from '../../state/selectors/conversations';
+import { deleteMessagesById } from '../../interactions/conversations/unsendingInteractions';
const AvatarItem = (props: { contact: ContactPropsMessageDetail }) => {
const { avatarPath, pubkey, name, profileName } = props.contact;
@@ -26,12 +26,12 @@ const DeleteButtonItem = (props: { messageId: string; convoId: string; isDeletab
return props.isDeletable ? (
) : null;
diff --git a/ts/components/conversation/message/MessageContentWithStatus.tsx b/ts/components/conversation/message/MessageContentWithStatus.tsx
index 4a15127f4..ac9896112 100644
--- a/ts/components/conversation/message/MessageContentWithStatus.tsx
+++ b/ts/components/conversation/message/MessageContentWithStatus.tsx
@@ -35,24 +35,9 @@ export const MessageContentWithStatuses = (props: Props) => {
const onClickOnMessageOuterContainer = useCallback(
(event: React.MouseEvent) => {
- const selection = window.getSelection();
- // Text is being selected
- if (selection && selection.type === 'Range') {
- return;
- }
-
- // User clicked on message body
- const target = event.target as HTMLDivElement;
- if (
- (!multiSelectMode && target.className === 'text-selectable') ||
- window.contextMenuShown ||
- props?.isDetailView
- ) {
- return;
- }
- event.preventDefault();
- event.stopPropagation();
- if (messageId) {
+ if (multiSelectMode && messageId) {
+ event.preventDefault();
+ event.stopPropagation();
dispatch(toggleSelectedMessageId(messageId));
}
},
diff --git a/ts/components/conversation/message/MessageContextMenu.tsx b/ts/components/conversation/message/MessageContextMenu.tsx
index b4d8c2bc5..f86de1ea8 100644
--- a/ts/components/conversation/message/MessageContextMenu.tsx
+++ b/ts/components/conversation/message/MessageContextMenu.tsx
@@ -4,7 +4,7 @@ import { animation, Item, Menu } from 'react-contexify';
import { MessageInteraction } from '../../../interactions';
import { getMessageById } from '../../../data/data';
-import { deleteMessagesById, replyToMessage } from '../../../interactions/conversationInteractions';
+import { replyToMessage } from '../../../interactions/conversationInteractions';
import {
showMessageDetailsView,
toggleSelectedMessageId,
@@ -16,8 +16,12 @@ import {
} from '../../../interactions/messageInteractions';
import { MessageRenderingProps } from '../../../models/messageType';
import { pushUnblockToSend } from '../../../session/utils/Toast';
-import { useSelector } from 'react-redux';
+import { useDispatch, useSelector } from 'react-redux';
import { getMessageContextMenuProps } from '../../../state/selectors/conversations';
+import {
+ deleteMessagesById,
+ deleteMessagesByIdForEveryone,
+} from '../../../interactions/conversations/unsendingInteractions';
export type MessageContextMenuSelectorProps = Pick<
MessageRenderingProps,
@@ -35,6 +39,7 @@ export type MessageContextMenuSelectorProps = Pick<
| 'serverTimestamp'
| 'timestamp'
| 'isBlocked'
+ | 'isDeletableForEveryone'
>;
type Props = { messageId: string; contextMenuId: string };
@@ -42,6 +47,7 @@ type Props = { messageId: string; contextMenuId: string };
// tslint:disable: max-func-body-length cyclomatic-complexity
export const MessageContextMenu = (props: Props) => {
const selected = useSelector(state => getMessageContextMenuProps(state as any, props.messageId));
+ const dispatch = useDispatch();
if (!selected) {
return null;
@@ -53,6 +59,7 @@ export const MessageContextMenu = (props: Props) => {
direction,
status,
isDeletable,
+ isDeletableForEveryone,
isPublic,
isOpenGroupV2,
weAreAdmin,
@@ -85,14 +92,15 @@ export const MessageContextMenu = (props: Props) => {
const found = await getMessageById(messageId);
if (found) {
const messageDetailsProps = await found.getPropsForMessageDetail();
- window.inboxStore?.dispatch(showMessageDetailsView(messageDetailsProps));
+ dispatch(showMessageDetailsView(messageDetailsProps));
} else {
window.log.warn(`Message ${messageId} not found in db`);
}
};
const selectMessageText = window.i18n('selectMessage');
- const deleteMessageText = window.i18n('deleteMessage');
+ const deleteMessageJustForMeText = window.i18n('deleteJustForMe');
+ const unsendMessageText = window.i18n('deleteForEveryone');
const addModerator = useCallback(() => {
void addSenderAsModerator(authorPhoneNumber, convoId);
@@ -151,11 +159,15 @@ export const MessageContextMenu = (props: Props) => {
}, [authorPhoneNumber, convoId]);
const onSelect = useCallback(() => {
- window.inboxStore?.dispatch(toggleSelectedMessageId(messageId));
+ dispatch(toggleSelectedMessageId(messageId));
}, [messageId]);
const onDelete = useCallback(() => {
- void deleteMessagesById([messageId], convoId, true);
+ void deleteMessagesById([messageId], convoId);
+ }, [convoId, messageId]);
+
+ const onDeleteForEveryone = useCallback(() => {
+ void deleteMessagesByIdForEveryone([messageId], convoId);
}, [convoId, messageId]);
return (
@@ -176,7 +188,16 @@ export const MessageContextMenu = (props: Props) => {
{isDeletable ? (
<>
- {selectMessageText}
- - {deleteMessageText}
+ >
+ ) : null}
+ {isDeletable && !isPublic ? (
+ <>
+ - {deleteMessageJustForMeText}
+ >
+ ) : null}
+ {isDeletableForEveryone ? (
+ <>
+ - {unsendMessageText}
>
) : null}
{weAreAdmin && isPublic ? - {window.i18n('banUser')}
: null}
diff --git a/ts/components/session/conversation/SessionRightPanel.tsx b/ts/components/session/conversation/SessionRightPanel.tsx
index 6fee96189..5d236d766 100644
--- a/ts/components/session/conversation/SessionRightPanel.tsx
+++ b/ts/components/session/conversation/SessionRightPanel.tsx
@@ -13,7 +13,7 @@ import {
} from '../../../data/data';
import { SpacerLG } from '../../basic/Text';
import {
- deleteMessagesByConvoIdWithConfirmation,
+ deleteAllMessagesByConvoIdWithConfirmation,
setDisappearingMessagesByConvoId,
showAddModeratorsByConvoId,
showInviteContactByConvoId,
@@ -249,7 +249,7 @@ export const SessionRightPanelWithDetails = () => {
const deleteConvoAction = isPublic
? () => {
- deleteMessagesByConvoIdWithConfirmation(id);
+ deleteAllMessagesByConvoIdWithConfirmation(id);
}
: () => {
showLeaveGroupByConvoId(id);
diff --git a/ts/components/session/menu/ConversationHeaderMenu.tsx b/ts/components/session/menu/ConversationHeaderMenu.tsx
index 43aea0ae4..248ad78ca 100644
--- a/ts/components/session/menu/ConversationHeaderMenu.tsx
+++ b/ts/components/session/menu/ConversationHeaderMenu.tsx
@@ -78,7 +78,7 @@ const ConversationHeaderMenu = (props: PropsConversationHeaderMenu) => {
{getMarkAllReadMenuItem(conversationId)}
{getChangeNicknameMenuItem(isMe, isGroup, conversationId)}
{getClearNicknameMenuItem(isMe, hasNickname, isGroup, conversationId)}
- {getDeleteMessagesMenuItem(isPublic, conversationId)}
+ {getDeleteMessagesMenuItem(conversationId)}
{getAddModeratorsMenuItem(weAreAdmin, isPublic, isKickedFromGroup, conversationId)}
{getRemoveModeratorsMenuItem(weAreAdmin, isPublic, isKickedFromGroup, conversationId)}
{getUpdateGroupNameMenuItem(weAreAdmin, isKickedFromGroup, left, conversationId)}
diff --git a/ts/components/session/menu/ConversationListItemContextMenu.tsx b/ts/components/session/menu/ConversationListItemContextMenu.tsx
index 0dc4d65bd..da9953af4 100644
--- a/ts/components/session/menu/ConversationListItemContextMenu.tsx
+++ b/ts/components/session/menu/ConversationListItemContextMenu.tsx
@@ -76,7 +76,7 @@ const ConversationListItemContextMenu = (props: PropsContextConversationItem) =>
{getMarkAllReadMenuItem(conversationId)}
{getChangeNicknameMenuItem(isMe, isGroup, conversationId)}
{getClearNicknameMenuItem(isMe, hasNickname, isGroup, conversationId)}
- {getDeleteMessagesMenuItem(isPublic, conversationId)}
+ {getDeleteMessagesMenuItem(conversationId)}
{getInviteContactMenuItem(isGroup, isPublic, conversationId)}
{getDeleteContactMenuItem(isGroup, isPublic, left, isKickedFromGroup, conversationId)}
{getLeaveGroupMenuItem(isKickedFromGroup, left, isGroup, isPublic, conversationId)}
diff --git a/ts/components/session/menu/Menu.tsx b/ts/components/session/menu/Menu.tsx
index b7fb86cd1..17f5606d5 100644
--- a/ts/components/session/menu/Menu.tsx
+++ b/ts/components/session/menu/Menu.tsx
@@ -23,7 +23,7 @@ import {
blockConvoById,
clearNickNameByConvoId,
copyPublicKeyByConvoId,
- deleteMessagesByConvoIdWithConfirmation,
+ deleteAllMessagesByConvoIdWithConfirmation,
markAllReadByConvoId,
setDisappearingMessagesByConvoId,
setNotificationForConvoId,
@@ -70,10 +70,6 @@ function showChangeNickname(isMe: boolean, isGroup: boolean) {
return !isMe && !isGroup;
}
-function showDeleteMessages(isPublic: boolean): boolean {
- return !isPublic;
-}
-
// we want to show the copyId for open groups and private chats only
function showCopyId(isPublic: boolean, isGroup: boolean): boolean {
return !isGroup || isPublic;
@@ -546,20 +542,16 @@ export function getChangeNicknameMenuItem(
return null;
}
-export function getDeleteMessagesMenuItem(
- isPublic: boolean | undefined,
- conversationId: string
-): JSX.Element | null {
- if (showDeleteMessages(Boolean(isPublic))) {
- return (
- - {
- deleteMessagesByConvoIdWithConfirmation(conversationId);
- }}
- >
- {window.i18n('deleteMessages')}
-
- );
- }
+export function getDeleteMessagesMenuItem(conversationId: string): JSX.Element | null {
+ return (
+ - {
+ deleteAllMessagesByConvoIdWithConfirmation(conversationId);
+ }}
+ >
+ {window.i18n('deleteMessages')}
+
+ );
+
return null;
}
diff --git a/ts/interactions/conversationInteractions.ts b/ts/interactions/conversationInteractions.ts
index 3160ed6db..1ce06cd3e 100644
--- a/ts/interactions/conversationInteractions.ts
+++ b/ts/interactions/conversationInteractions.ts
@@ -5,13 +5,7 @@ import {
} from '../opengroup/utils/OpenGroupUtils';
import { getV2OpenGroupRoom } from '../data/opengroups';
import { SyncUtils, ToastUtils, UserUtils } from '../session/utils';
-import {
- ConversationModel,
- ConversationNotificationSettingType,
- ConversationTypeEnum,
-} from '../models/conversation';
-import { MessageModel } from '../models/message';
-import { ApiV2 } from '../opengroup/opengroupV2';
+import { ConversationNotificationSettingType, ConversationTypeEnum } from '../models/conversation';
import _ from 'lodash';
import { getConversationController } from '../session/conversations';
@@ -32,11 +26,7 @@ import {
lastAvatarUploadTimestamp,
removeAllMessagesInConversation,
} from '../data/data';
-import {
- conversationReset,
- quoteMessage,
- resetSelectedMessageIds,
-} from '../state/ducks/conversations';
+import { conversationReset, quoteMessage } from '../state/ducks/conversations';
import { getDecryptedMediaUrl } from '../session/crypto/DecryptedAttachmentsManager';
import { IMAGE_JPEG } from '../types/MIME';
import { FSv2 } from '../fileserver';
@@ -86,61 +76,6 @@ export async function copyPublicKeyByConvoId(convoId: string) {
ToastUtils.pushCopiedToClipBoard();
}
-/**
- *
- * @param messages the list of MessageModel to delete
- * @param convo the conversation to delete from (only v2 opengroups are supported)
- */
-async function deleteOpenGroupMessages(
- messages: Array,
- convo: ConversationModel
-): Promise> {
- if (!convo.isPublic()) {
- throw new Error('cannot delete public message on a non public groups');
- }
-
- if (convo.isOpenGroupV2()) {
- const roomInfos = convo.toOpenGroupV2();
- // on v2 servers we can only remove a single message per request..
- // so logic here is to delete each messages and get which one where not removed
- const validServerIdsToRemove = _.compact(
- messages.map(msg => {
- return msg.get('serverId');
- })
- );
-
- const validMessageModelsToRemove = _.compact(
- messages.map(msg => {
- const serverId = msg.get('serverId');
- if (serverId) {
- return msg;
- }
- return undefined;
- })
- );
-
- let allMessagesAreDeleted: boolean = false;
- if (validServerIdsToRemove.length) {
- allMessagesAreDeleted = await ApiV2.deleteMessageByServerIds(
- validServerIdsToRemove,
- roomInfos
- );
- }
- // remove only the messages we managed to remove on the server
- if (allMessagesAreDeleted) {
- window?.log?.info('Removed all those serverIds messages successfully');
- return validMessageModelsToRemove.map(m => m.id as string);
- } else {
- window?.log?.info(
- 'failed to remove all those serverIds message. not removing them locally neither'
- );
- return [];
- }
- } else {
- throw new Error('Opengroupv1 are not supported anymore');
- }
-}
-
export async function blockConvoById(conversationId: string) {
const conversation = getConversationController().get(conversationId);
@@ -286,7 +221,7 @@ export function showChangeNickNameByConvoId(conversationId: string) {
window.inboxStore?.dispatch(changeNickNameModal({ conversationId }));
}
-export async function deleteMessagesByConvoIdNoConfirmation(conversationId: string) {
+export async function deleteAllMessagesByConvoIdNoConfirmation(conversationId: string) {
const conversation = getConversationController().get(conversationId);
await removeAllMessagesInConversation(conversationId);
window.inboxStore?.dispatch(conversationReset(conversationId));
@@ -302,13 +237,13 @@ export async function deleteMessagesByConvoIdNoConfirmation(conversationId: stri
await conversation.commit();
}
-export function deleteMessagesByConvoIdWithConfirmation(conversationId: string) {
+export function deleteAllMessagesByConvoIdWithConfirmation(conversationId: string) {
const onClickClose = () => {
window?.inboxStore?.dispatch(updateConfirmModal(null));
};
const onClickOk = async () => {
- await deleteMessagesByConvoIdNoConfirmation(conversationId);
+ await deleteAllMessagesByConvoIdNoConfirmation(conversationId);
onClickClose();
};
@@ -435,196 +370,6 @@ export async function uploadOurAvatar(newAvatarDecrypted?: ArrayBuffer) {
}
}
-/**
- * Deletes messages for everyone in a 1-1 or closed group conversation
- * @param msgsToDelete Messages to delete
- */
-async function deleteForAll(conversation: ConversationModel, msgsToDelete: Array) {
- window?.log?.warn('Deleting messages for all users in this conversation');
- const result = await conversation.unsendMessages(msgsToDelete);
- // TODO: may need to specify deletion for own device as well.
- window.inboxStore?.dispatch(resetSelectedMessageIds());
- if (result) {
- ToastUtils.pushDeleted();
- } else {
- ToastUtils.someDeletionsFailed();
- }
-}
-
-/**
- *
- * @param toDeleteLocallyIds Messages to delete for just this user. Still sends an unsend message to sync
- * with other devices
- */
-async function deleteForJustThisUser(
- conversation: ConversationModel,
- msgsToDelete: Array
-) {
- window?.log?.warn('Deleting messages just for this user');
- // is deleting on swarm sufficient or does it need to be unsent as well?
- const deleteResult = await conversation.deleteMessages(msgsToDelete);
- // Update view and trigger update
- window.inboxStore?.dispatch(resetSelectedMessageIds());
- if (deleteResult) {
- ToastUtils.pushDeleted();
- } else {
- ToastUtils.someDeletionsFailed();
- }
-}
-
-const doDeleteMessagesById = async (
- selectedMessages: Array,
- conversation: ConversationModel,
- deleteForEveryone: boolean = true
-) => {
- let toDeleteLocallyIds: Array;
-
- const ourDevicePubkey = UserUtils.getOurPubKeyStrFromCache();
- if (!ourDevicePubkey) {
- return;
- }
- const isServerDeletable = conversation.isPublic();
-
- const isAllOurs = selectedMessages.every(message => ourDevicePubkey === message.getSource());
- if (isServerDeletable) {
- //#region open group v2 deletion
- // Get our Moderator status
- const isAdmin = conversation.isAdmin(ourDevicePubkey);
-
- if (!isAllOurs && !isAdmin) {
- ToastUtils.pushMessageDeleteForbidden();
-
- window.inboxStore?.dispatch(resetSelectedMessageIds());
- return;
- }
-
- toDeleteLocallyIds = await deleteOpenGroupMessages(selectedMessages, conversation);
- if (toDeleteLocallyIds.length === 0) {
- // Message failed to delete from server, show error?
- return;
- }
- // successful deletion
- ToastUtils.pushDeleted();
- window.inboxStore?.dispatch(resetSelectedMessageIds());
- //#endregion
- } else {
- //#region deletion for 1-1 and closed groups
- if (!isAllOurs) {
- ToastUtils.pushMessageDeleteForbidden();
- window.inboxStore?.dispatch(resetSelectedMessageIds());
- return;
- }
-
- if (window.lokiFeatureFlags?.useUnsendRequests) {
- if (deleteForEveryone) {
- void deleteForAll(conversation, selectedMessages);
- } else {
- void deleteForJustThisUser(conversation, selectedMessages);
- }
- } else {
- //#region to remove once unsend enabled
- const messageIds = selectedMessages.map(m => m.id) as Array;
- await Promise.all(messageIds.map(msgId => conversation.removeMessage(msgId)));
- ToastUtils.pushDeleted();
- window.inboxStore?.dispatch(resetSelectedMessageIds());
- //#endregion
- }
- //#endregion
- }
-};
-
-// tslint:disable-next-line: max-func-body-length
-export async function deleteMessagesById(
- messageIds: Array,
- conversationId: string,
- askUserForConfirmation: boolean
-) {
- const conversation = getConversationController().getOrThrow(conversationId);
- const selectedMessages = _.compact(
- await Promise.all(messageIds.map(m => getMessageById(m, false)))
- );
-
- const moreThanOne = selectedMessages.length > 1;
-
- // In future, we may be able to unsend private messages also
- // isServerDeletable also defined in ConversationHeader.tsx for
- // future reference
- const isServerDeletable = conversation.isPublic();
-
- if (askUserForConfirmation) {
- let title = '';
-
- // Note: keep that i18n logic separated so the scripts in tools/ find the usage of those
- if (isServerDeletable) {
- if (moreThanOne) {
- title = window.i18n('deleteMessagesForEveryone');
- } else {
- title = window.i18n('deleteMessageForEveryone');
- }
- } else {
- if (moreThanOne) {
- title = window.i18n('deleteMessages');
- } else {
- title = window.i18n('deleteMessage');
- }
- }
-
- const okText = window.i18n(isServerDeletable ? 'deleteForEveryone' : 'delete');
-
- //#region confirmation for deletion of messages
- const showDeletionTypeModal = () => {
- window.inboxStore?.dispatch(updateConfirmModal(null));
- window.inboxStore?.dispatch(
- updateConfirmModal({
- showExitIcon: true,
- title: window.i18n('deletionTypeTitle'),
- okText: window.i18n('deleteMessageForEveryoneLowercase'),
- okTheme: SessionButtonColor.Danger,
- onClickOk: async () => {
- await doDeleteMessagesById(selectedMessages, conversation, true);
- },
- cancelText: window.i18n('deleteJustForMe'),
- onClickCancel: async () => {
- await doDeleteMessagesById(selectedMessages, conversation, false);
- },
- onClickClose: () => {
- window.inboxStore?.dispatch(updateConfirmModal(null));
- },
- })
- );
- return;
- };
-
- window.inboxStore?.dispatch(
- updateConfirmModal({
- title,
- message: window.i18n(moreThanOne ? 'deleteMessagesQuestion' : 'deleteMessageQuestion'),
- okText,
- okTheme: SessionButtonColor.Danger,
- onClickOk: async () => {
- if (isServerDeletable) {
- // unsend logic
- await doDeleteMessagesById(selectedMessages, conversation, true);
- // explicity close modal for this case.
- window.inboxStore?.dispatch(updateConfirmModal(null));
- return;
- }
- if (window.lokiFeatureFlags?.useUnsendRequests) {
- showDeletionTypeModal();
- } else {
- await doDeleteMessagesById(selectedMessages, conversation, false);
- window.inboxStore?.dispatch(updateConfirmModal(null));
- }
- },
- closeAfterInput: false,
- })
- );
- //#endregion
- } else {
- void doDeleteMessagesById(selectedMessages, conversation);
- }
-}
-
export async function replyToMessage(messageId: string) {
const quotedMessageModel = await getMessageById(messageId);
if (!quotedMessageModel) {
diff --git a/ts/interactions/conversations/unsendingInteractions.ts b/ts/interactions/conversations/unsendingInteractions.ts
new file mode 100644
index 000000000..414e2a7c6
--- /dev/null
+++ b/ts/interactions/conversations/unsendingInteractions.ts
@@ -0,0 +1,366 @@
+import _ from 'underscore';
+import { SessionButtonColor } from '../../components/session/SessionButton';
+import { getMessageById } from '../../data/data';
+import { ConversationModel } from '../../models/conversation';
+import { MessageModel } from '../../models/message';
+import { ApiV2 } from '../../opengroup/opengroupV2';
+import { getMessageQueue } from '../../session';
+import { getConversationController } from '../../session/conversations';
+import { UnsendMessage } from '../../session/messages/outgoing/controlMessage/UnsendMessage';
+import { networkDeleteMessages } from '../../session/snode_api/SNodeAPI';
+import { PubKey } from '../../session/types';
+import { ToastUtils, UserUtils } from '../../session/utils';
+import { resetSelectedMessageIds } from '../../state/ducks/conversations';
+import { updateConfirmModal } from '../../state/ducks/modalDialog';
+
+/**
+ * Deletes messages for everyone in a 1-1 or everyone in a closed group conversation.
+ */
+async function unsendMessagesForEveryone(
+ conversation: ConversationModel,
+ msgsToDelete: Array
+) {
+ window?.log?.info('Deleting messages for all users in this conversation');
+ const destinationId = conversation.id;
+ if (!destinationId) {
+ return;
+ }
+ if (conversation.isOpenGroupV2()) {
+ throw new Error(
+ 'Cannot unsend a message for an opengroup v2. This has to be a deleteMessage api call'
+ );
+ }
+ const unsendMsgObjects = getUnsendMessagesObjects(msgsToDelete);
+
+ let allDeleted = false;
+ if (conversation.isPrivate()) {
+ // sending to recipient all the messages separately for now
+ await Promise.all(
+ unsendMsgObjects.map(unsendObject =>
+ getMessageQueue()
+ .sendToPubKey(new PubKey(destinationId), unsendObject)
+ .catch(window?.log?.error)
+ )
+ );
+ allDeleted = await deleteMessagesFromSwarmAndCompletelyLocally(conversation, msgsToDelete);
+ } else if (conversation.isClosedGroup()) {
+ // sending to recipient all the messages separately for now
+ await Promise.all(
+ unsendMsgObjects.map(unsendObject =>
+ getMessageQueue()
+ .sendToGroup(unsendObject, undefined, new PubKey(destinationId))
+ .catch(window?.log?.error)
+ )
+ );
+
+ allDeleted = await deleteMessagesFromSwarmAndCompletelyLocally(conversation, msgsToDelete);
+
+ return;
+ }
+
+ window.inboxStore?.dispatch(resetSelectedMessageIds());
+ if (allDeleted) {
+ ToastUtils.pushDeleted();
+ } else {
+ ToastUtils.someDeletionsFailed();
+ }
+}
+
+function getUnsendMessagesObjects(messages: Array) {
+ //#region building request
+ return _.compact(
+ messages.map(message => {
+ const author = message.get('source');
+
+ // call getPropsForMessage here so we get the received_at or sent_at timestamp in timestamp
+ const timestamp = message.getPropsForMessage().timestamp;
+ if (!timestamp) {
+ window?.log?.error('cannot find timestamp - aborting unsend request');
+ return undefined;
+ }
+
+ const unsendParams = {
+ timestamp,
+ author,
+ };
+
+ return new UnsendMessage(unsendParams);
+ })
+ );
+ //#endregion
+}
+
+/**
+ * Do a single request to the swarm with all the message hashes to delete from the swarm.
+ *
+ * It does not delete anything locally.
+ */
+export async function deleteMessagesFromSwarmOnly(messages: Array) {
+ try {
+ const deletionMessageHashes = _.compact(messages.map(m => m.get('messageHash')));
+ if (deletionMessageHashes.length > 0) {
+ await networkDeleteMessages(deletionMessageHashes);
+ }
+ } catch (e) {
+ window.log?.error('Error deleting message from swarm', e);
+ return false;
+ }
+ return true;
+}
+
+/**
+ * Delete the messages from the swarm with an unsend request and if it worked, delete those messages locally.
+ * If an error happened, we just return false, Toast an error, and do not remove the messages locally at all.
+ */
+async function deleteMessagesFromSwarmAndCompletelyLocally(
+ conversation: ConversationModel,
+ messages: Array
+) {
+ const deletedFromSwarm = await deleteMessagesFromSwarmOnly(messages);
+ if (!deletedFromSwarm) {
+ window.log.warn(
+ 'deleteMessagesFromSwarmAndCompletelyLocally: some messages failed to be deleted '
+ );
+ return false;
+ }
+ await Promise.all(
+ messages.map(async message => {
+ return deleteMessageLocallyOnly({ conversation, message, deletionType: 'complete' });
+ })
+ );
+ return true;
+}
+
+/**
+ * Deletes a message completely or mark it as deleted only. Does not interact with the swarm at all
+ * @param message Message to delete
+ * @param deletionType 'complete' means completely delete the item from the database, markDeleted means empty the message content but keep an entry
+ */
+export async function deleteMessageLocallyOnly({
+ conversation,
+ message,
+ deletionType,
+}: {
+ conversation: ConversationModel;
+ message: MessageModel;
+ deletionType: 'complete' | 'markDeleted';
+}) {
+ if (deletionType === 'complete') {
+ // remove the message from the database
+ await conversation.removeMessage(message.get('id'));
+ } else {
+ // just mark the message as deleted but still show in conversation
+ await message.markAsDeleted();
+ await message.markRead(Date.now());
+ }
+ conversation.updateLastMessage();
+}
+
+/**
+ *
+ */
+async function deleteJustForThisUser(
+ conversation: ConversationModel,
+ msgsToDelete: Array
+) {
+ window?.log?.warn('Deleting messages just for this user');
+ // is deleting on swarm sufficient or does it need to be unsent as well?
+ const deleteResult = await deleteMessagesFromSwarmAndCompletelyLocally(
+ conversation,
+ msgsToDelete
+ );
+ // Update view and trigger update
+ window.inboxStore?.dispatch(resetSelectedMessageIds());
+ if (deleteResult) {
+ ToastUtils.pushDeleted();
+ } else {
+ ToastUtils.someDeletionsFailed();
+ }
+}
+
+const doDeleteSelectedMessagesInSOGS = async (
+ selectedMessages: Array,
+ conversation: ConversationModel,
+ isAllOurs: boolean
+) => {
+ const ourDevicePubkey = UserUtils.getOurPubKeyStrFromCache();
+ if (!ourDevicePubkey) {
+ return;
+ }
+ //#region open group v2 deletion
+ // Get our Moderator status
+ const isAdmin = conversation.isAdmin(ourDevicePubkey);
+
+ if (!isAllOurs && !isAdmin) {
+ ToastUtils.pushMessageDeleteForbidden();
+ window.inboxStore?.dispatch(resetSelectedMessageIds());
+ return;
+ }
+
+ const toDeleteLocallyIds = await deleteOpenGroupMessages(selectedMessages, conversation);
+ if (toDeleteLocallyIds.length === 0) {
+ // Message failed to delete from server, show error?
+ return;
+ }
+ await Promise.all(
+ toDeleteLocallyIds.map(async id => {
+ const msgToDeleteLocally = await getMessageById(id);
+ if (msgToDeleteLocally) {
+ return deleteMessageLocallyOnly({
+ conversation,
+ message: msgToDeleteLocally,
+ deletionType: 'complete',
+ });
+ }
+ })
+ );
+ // successful deletion
+ ToastUtils.pushDeleted();
+ window.inboxStore?.dispatch(resetSelectedMessageIds());
+ //#endregion
+};
+
+/**
+ * Effectively delete the messages from a conversation.
+ * This call is to be called by the user on a confirmation dialog for instance.
+ *
+ * It does what needs to be done on a user action to delete messages for each conversation type
+ */
+const doDeleteSelectedMessages = async (
+ selectedMessages: Array,
+ conversation: ConversationModel,
+ shouldDeleteForEveryone: boolean
+) => {
+ const ourDevicePubkey = UserUtils.getOurPubKeyStrFromCache();
+ if (!ourDevicePubkey) {
+ return;
+ }
+
+ const isAllOurs = selectedMessages.every(message => ourDevicePubkey === message.getSource());
+ if (conversation.isPublic()) {
+ return doDeleteSelectedMessagesInSOGS(selectedMessages, conversation, isAllOurs);
+ }
+
+ //#region deletion for 1-1 and closed groups
+ if (!isAllOurs) {
+ ToastUtils.pushMessageDeleteForbidden();
+ window.inboxStore?.dispatch(resetSelectedMessageIds());
+ return;
+ }
+
+ if (shouldDeleteForEveryone) {
+ return unsendMessagesForEveryone(conversation, selectedMessages);
+ }
+ return deleteJustForThisUser(conversation, selectedMessages);
+
+ //#endregion
+};
+
+// tslint:disable-next-line: max-func-body-length
+export async function deleteMessagesByIdForEveryone(
+ messageIds: Array,
+ conversationId: string
+) {
+ const conversation = getConversationController().getOrThrow(conversationId);
+ const selectedMessages = _.compact(
+ await Promise.all(messageIds.map(m => getMessageById(m, false)))
+ );
+
+ const moreThanOne = selectedMessages.length > 1;
+
+ window.inboxStore?.dispatch(
+ updateConfirmModal({
+ title: window.i18n('deleteForEveryone'),
+ message: moreThanOne
+ ? window.i18n('deleteMessagesQuestion')
+ : window.i18n('deleteMessageQuestion'),
+ okText: window.i18n('deleteForEveryone'),
+ okTheme: SessionButtonColor.Danger,
+ onClickOk: async () => {
+ await doDeleteSelectedMessages(selectedMessages, conversation, true);
+
+ // explicity close modal for this case.
+ window.inboxStore?.dispatch(updateConfirmModal(null));
+ return;
+ },
+ closeAfterInput: false,
+ })
+ );
+}
+
+// tslint:disable-next-line: max-func-body-length
+export async function deleteMessagesById(messageIds: Array, conversationId: string) {
+ const conversation = getConversationController().getOrThrow(conversationId);
+ const selectedMessages = _.compact(
+ await Promise.all(messageIds.map(m => getMessageById(m, false)))
+ );
+
+ const moreThanOne = selectedMessages.length > 1;
+
+ window.inboxStore?.dispatch(
+ updateConfirmModal({
+ title: window.i18n('deleteJustForMe'),
+ message: moreThanOne
+ ? window.i18n('deleteMessagesQuestion')
+ : window.i18n('deleteMessageQuestion'),
+ okText: window.i18n('delete'),
+ okTheme: SessionButtonColor.Danger,
+ onClickOk: async () => {
+ await doDeleteSelectedMessages(selectedMessages, conversation, false);
+ },
+ closeAfterInput: false,
+ })
+ );
+}
+
+/**
+ *
+ * @param messages the list of MessageModel to delete
+ * @param convo the conversation to delete from (only v2 opengroups are supported)
+ */
+async function deleteOpenGroupMessages(
+ messages: Array,
+ convo: ConversationModel
+): Promise> {
+ if (!convo.isPublic()) {
+ throw new Error('cannot delete public message on a non public groups');
+ }
+
+ if (!convo.isOpenGroupV2()) {
+ throw new Error('Opengroupv1 are not supported anymore');
+ }
+
+ const roomInfos = convo.toOpenGroupV2();
+ // on v2 servers we can only remove a single message per request..
+ // so logic here is to delete each messages and get which one where not removed
+ const validServerIdsToRemove = _.compact(
+ messages.map(msg => {
+ return msg.get('serverId');
+ })
+ );
+
+ const validMessageModelsToRemove = _.compact(
+ messages.map(msg => {
+ const serverId = msg.get('serverId');
+ if (serverId) {
+ return msg;
+ }
+ return undefined;
+ })
+ );
+
+ let allMessagesAreDeleted: boolean = false;
+ if (validServerIdsToRemove.length) {
+ allMessagesAreDeleted = await ApiV2.deleteMessageByServerIds(validServerIdsToRemove, roomInfos);
+ }
+ // remove only the messages we managed to remove on the server
+ if (allMessagesAreDeleted) {
+ window?.log?.info('Removed all those serverIds messages successfully');
+ return validMessageModelsToRemove.map(m => m.id as string);
+ } else {
+ window?.log?.info(
+ 'failed to remove all those serverIds message. not removing them locally neither'
+ );
+ return [];
+ }
+}
diff --git a/ts/models/conversation.ts b/ts/models/conversation.ts
index 8eee66971..b48ba361d 100644
--- a/ts/models/conversation.ts
+++ b/ts/models/conversation.ts
@@ -48,8 +48,7 @@ import {
import { ed25519Str } from '../session/onions/onionPath';
import { getDecryptedMediaUrl } from '../session/crypto/DecryptedAttachmentsManager';
import { IMAGE_JPEG } from '../types/MIME';
-import { UnsendMessage } from '../session/messages/outgoing/controlMessage/UnsendMessage';
-import { getLatestTimestampOffset, networkDeleteMessages } from '../session/snode_api/SNodeAPI';
+import { getLatestTimestampOffset } from '../session/snode_api/SNodeAPI';
export enum ConversationTypeEnum {
GROUP = 'group',
@@ -800,127 +799,6 @@ export class ConversationModel extends Backbone.Model {
}
}
- /**
- * @param messages Messages to delete
- */
- public async deleteMessages(messages: Array) {
- const results = await Promise.all(
- messages.map(async message => {
- return this.deleteMessage(message, true);
- })
- );
- return _.every(results);
- }
-
- /**
- * Deletes message from this device's swarm and handles local deletion of message
- * @param message Message to delete
- * @param removeFromDatabase delete message from the database entirely or just modify the message data
- * @returns boolean if the deletion succeeeded
- */
- public async deleteMessage(message: MessageModel, removeFromDatabase = false): Promise {
- //#region deletion on network
- try {
- const deletionMessageHashes = _.compact([message.get('messageHash')]);
- if (deletionMessageHashes.length > 0) {
- await networkDeleteMessages(deletionMessageHashes);
- }
- } catch (e) {
- window.log?.error('Error deleting message from swarm', e);
- return false;
- }
- //#endregion
-
- //#region handling database
- if (removeFromDatabase) {
- // remove the message from the database
- await this.removeMessage(message.get('id'));
- } else {
- // just mark the message as deleted but still show in conversation
- await message.markAsDeleted();
- await message.markRead(Date.now());
- this.updateLastMessage();
- }
- //#endregion
- return true;
- }
-
- public async unsendMessages(messages: Array) {
- const results = await Promise.all(
- messages.map(async message => {
- return this.unsendMessage(message, false);
- })
- );
- return _.every(results);
- }
-
- /**
- * Creates an unsend request using protobuf and adds to messageQueue.
- * @param message Message to unsend
- */
- public async unsendMessage(
- message: MessageModel,
- onlyDeleteForSender: boolean = false
- ): Promise {
- if (!message.get('messageHash')) {
- window?.log?.error(`message ${message.id}, cannot find hash: ${message.get('messageHash')}`);
- return false;
- }
- const ownPrimaryDevicePubkey = UserUtils.getOurPubKeyFromCache();
-
- // If deleting just for sender, set destination to sender
- const destinationId = onlyDeleteForSender ? ownPrimaryDevicePubkey : this.id;
- if (!destinationId) {
- return false;
- }
- //#endregion
-
- //#region building request
- const author = message.get('source');
-
- // call getPropsForMessage here so we get the received_at or sent_at timestamp in timestamp
- const timestamp = message.getPropsForMessage().timestamp;
- if (!timestamp) {
- window?.log?.error('cannot find timestamp - aborting unsend request');
- return false;
- }
-
- const unsendParams = {
- timestamp,
- author,
- };
-
- const unsendMessage = new UnsendMessage(unsendParams);
- //#endregion
-
- //#region sending
- // 1-1 Session
- if (!this.isGroup()) {
- // sending to recipient
- await getMessageQueue()
- .sendToPubKey(new PubKey(destinationId), unsendMessage)
- .catch(window?.log?.error);
- return this.deleteMessage(message);
- }
-
- // closed groups
- if (this.isClosedGroup() && this.id) {
- await getMessageQueue()
- .sendToGroup(unsendMessage, undefined, PubKey.cast(this.id))
- .catch(window?.log?.error);
- // not calling deleteMessage as it'll be called by the unsend handler when it's received
- return true;
- }
-
- // open groups
- if (this.isOpenGroupV2()) {
- window?.log?.info('Conversation is open group. Skipping unsend request.');
- }
-
- return true;
- //#endregion
- }
-
public async sendMessage(msg: SendMessageType) {
const { attachments, body, groupInvitation, preview, quote } = msg;
this.clearTypingTimers();
diff --git a/ts/models/message.ts b/ts/models/message.ts
index ce61f6b59..43b5a7f07 100644
--- a/ts/models/message.ts
+++ b/ts/models/message.ts
@@ -873,7 +873,7 @@ export class MessageModel extends Backbone.Model {
public async markAsDeleted() {
this.set({
isDeleted: true,
- body: window.i18n('messageDeleted'),
+ body: window.i18n('messageDeletedPlaceholder'),
quote: undefined,
groupInvitation: undefined,
dataExtractionNotification: undefined,
diff --git a/ts/receiver/contentMessage.ts b/ts/receiver/contentMessage.ts
index c3f900f33..73ce9ae2b 100644
--- a/ts/receiver/contentMessage.ts
+++ b/ts/receiver/contentMessage.ts
@@ -19,6 +19,10 @@ import { perfEnd, perfStart } from '../session/utils/Performance';
import { getAllCachedECKeyPair } from './closedGroups';
import { getMessageBySenderAndTimestamp } from '../data/data';
import { handleCallMessage } from './callMessage';
+import {
+ deleteMessageLocallyOnly,
+ deleteMessagesFromSwarmOnly,
+} from '../interactions/conversations/unsendingInteractions';
export async function handleContentMessage(envelope: EnvelopePlus, messageHash?: string) {
try {
@@ -397,7 +401,7 @@ export async function innerHandleContentMessage(
);
return;
}
- if (content.unsendMessage && window.lokiFeatureFlags?.useUnsendRequests) {
+ if (content.unsendMessage) {
await handleUnsendMessage(envelope, content.unsendMessage as SignalService.Unsend);
}
if (content.callMessage && window.lokiFeatureFlags?.useCallMessage) {
@@ -519,7 +523,20 @@ async function handleUnsendMessage(envelope: EnvelopePlus, unsendMessage: Signal
//#region executing deletion
if (messageHash && messageToDelete) {
- await conversation.deleteMessage(messageToDelete);
+ const wasDeleted = await deleteMessagesFromSwarmOnly([messageToDelete]);
+ if (!wasDeleted) {
+ window.log.warn(
+ 'handleUnsendMessage: got a request to delete ',
+ messageHash,
+ ' but an error happened during deleting it from our swarm.'
+ );
+ }
+ // still, delete it locally
+ await deleteMessageLocallyOnly({
+ conversation,
+ message: messageToDelete,
+ deletionType: 'markDeleted',
+ });
}
//#endregion
}
diff --git a/ts/session/conversations/ConversationController.ts b/ts/session/conversations/ConversationController.ts
index 08f3e39a0..6aacc13f3 100644
--- a/ts/session/conversations/ConversationController.ts
+++ b/ts/session/conversations/ConversationController.ts
@@ -18,7 +18,7 @@ import { getV2OpenGroupRoom, removeV2OpenGroupRoom } from '../../data/opengroups
import _ from 'lodash';
import { getOpenGroupManager } from '../../opengroup/opengroupV2/OpenGroupManagerV2';
import { deleteAuthToken, DeleteAuthTokenRequest } from '../../opengroup/opengroupV2/ApiAuth';
-import { deleteMessagesByConvoIdNoConfirmation } from '../../interactions/conversationInteractions';
+import { deleteAllMessagesByConvoIdNoConfirmation } from '../../interactions/conversationInteractions';
let instance: ConversationController | null;
@@ -228,7 +228,7 @@ export class ConversationController {
// those are the stuff to do for all contact types
window.log.info(`deleteContact destroyingMessages: ${id}`);
- await deleteMessagesByConvoIdNoConfirmation(conversation.id);
+ await deleteAllMessagesByConvoIdNoConfirmation(conversation.id);
window.log.info(`deleteContact message destroyed: ${id}`);
// if this conversation is a private conversation it's in fact a `contact` for desktop.
// we just want to remove everything related to it, set the active_at to undefined
diff --git a/ts/session/utils/CallManager.ts b/ts/session/utils/CallManager.ts
index 09e8012d9..2a13e5ea4 100644
--- a/ts/session/utils/CallManager.ts
+++ b/ts/session/utils/CallManager.ts
@@ -68,11 +68,7 @@ let ignoreOffer = false;
let isSettingRemoteAnswerPending = false;
let lastOutgoingOfferTimestamp = -Infinity;
-const configuration = {
- configuration: {
- offerToReceiveAudio: true,
- offerToReceiveVideo: true,
- },
+const configuration: RTCConfiguration = {
iceServers: [
{
urls: 'turn:freyr.getsession.org',
@@ -80,6 +76,7 @@ const configuration = {
credential: 'webrtc',
},
],
+ iceTransportPolicy: 'relay',
};
let selectedCameraId: string | undefined;
@@ -355,6 +352,7 @@ const iceSenderDebouncer = _.debounce(async (recipient: string) => {
sdpMids: validCandidates.map(c => c.sdpMid),
sdps: validCandidates.map(c => c.candidate),
});
+
window.log.info('sending ICE CANDIDATES MESSAGE to ', recipient);
await getMessageQueue().sendToPubKeyNonDurably(PubKey.cast(recipient), callIceCandicates);
diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts
index b4492c517..9143a3349 100644
--- a/ts/state/ducks/conversations.ts
+++ b/ts/state/ducks/conversations.ts
@@ -209,6 +209,7 @@ export type PropsForMessageWithConvoProps = PropsForMessageWithoutConvoProps & {
weAreAdmin: boolean;
isSenderAdmin: boolean;
isDeletable: boolean;
+ isDeletableForEveryone: boolean;
isBlocked: boolean;
isDeleted?: boolean;
};
diff --git a/ts/state/selectors/conversations.ts b/ts/state/selectors/conversations.ts
index dd6694d2e..4b5614aa7 100644
--- a/ts/state/selectors/conversations.ts
+++ b/ts/state/selectors/conversations.ts
@@ -74,6 +74,13 @@ export const getSelectedConversation = createSelector(
}
);
+export const getSelectedConversationIsPublic = createSelector(
+ getSelectedConversation,
+ (state: ReduxConversationType | undefined): boolean => {
+ return state?.isPublic || false;
+ }
+);
+
export const getHasIncomingCallFrom = createSelector(
getConversations,
(state: ConversationsStateType): ReduxConversationType | undefined => {
@@ -706,11 +713,18 @@ export const getMessagePropsByMessageId = createSelector(
const groupAdmins = (isGroup && foundMessageConversation.groupAdmins) || [];
const weAreAdmin = groupAdmins.includes(ourPubkey) || false;
- // a message is deletable if
+ // A message is deletable if
// either we sent it,
// or the convo is not a public one (in this case, we will only be able to delete for us)
// or the convo is public and we are an admin
const isDeletable = authorPhoneNumber === ourPubkey || !isPublic || (isPublic && !!weAreAdmin);
+
+ // A message is deletable for everyone if
+ // either we sent it no matter what the conversation type,
+ // or the convo is public and we are an admin
+ const isDeletableForEveryone =
+ authorPhoneNumber === ourPubkey || (isPublic && !!weAreAdmin) || false;
+
const isSenderAdmin = groupAdmins.includes(authorPhoneNumber);
const senderIsUs = authorPhoneNumber === ourPubkey;
@@ -726,6 +740,7 @@ export const getMessagePropsByMessageId = createSelector(
isOpenGroupV2: !!isPublic,
isSenderAdmin,
isDeletable,
+ isDeletableForEveryone,
weAreAdmin,
conversationType: foundMessageConversation.type,
authorPhoneNumber,
@@ -869,6 +884,7 @@ export const getMessageContextMenuProps = createSelector(getMessagePropsByMessag
serverTimestamp,
timestamp,
isBlocked,
+ isDeletableForEveryone,
} = props.propsForMessage;
const msgProps: MessageContextMenuSelectorProps = {
@@ -886,6 +902,7 @@ export const getMessageContextMenuProps = createSelector(getMessagePropsByMessag
serverTimestamp,
timestamp,
isBlocked,
+ isDeletableForEveryone,
};
return msgProps;
diff --git a/ts/window.d.ts b/ts/window.d.ts
index 7fb91f69e..2db947819 100644
--- a/ts/window.d.ts
+++ b/ts/window.d.ts
@@ -47,7 +47,6 @@ declare global {
useFileOnionRequestsV2: boolean;
padOutgoingAttachments: boolean;
enablePinConversations: boolean;
- useUnsendRequests: boolean;
useCallMessage: boolean;
};
lokiSnodeAPI: LokiSnodeAPI;