diff --git a/_locales/en/messages.json b/_locales/en/messages.json
index 132d92303..cb37315f7 100644
--- a/_locales/en/messages.json
+++ b/_locales/en/messages.json
@@ -100,6 +100,7 @@
"deleteMessagesQuestion": "Delete $count$ messages?",
"deleteMessageQuestion": "Delete this message?",
"deleteMessages": "Delete Messages",
+ "deleteConversation": "Delete Conversation",
"deleted": "$count$ deleted",
"messageDeletedPlaceholder": "This message has been deleted",
"from": "From:",
diff --git a/ts/components/dialog/AdminLeaveClosedGroupDialog.tsx b/ts/components/dialog/AdminLeaveClosedGroupDialog.tsx
index c42cd999f..567322b90 100644
--- a/ts/components/dialog/AdminLeaveClosedGroupDialog.tsx
+++ b/ts/components/dialog/AdminLeaveClosedGroupDialog.tsx
@@ -29,7 +29,9 @@ export const AdminLeaveClosedGroupDialog = (props: { conversationId: string }) =
}
setLoading(true);
// we know want to delete a closed group right after we've left it, so we can call the deleteContact which takes care of it all
- await getConversationController().deleteContact(props.conversationId, false);
+ await getConversationController().deleteContact(props.conversationId, {
+ fromSyncMessage: false,
+ });
setLoading(false);
closeDialog();
};
diff --git a/ts/components/menu/ConversationHeaderMenu.tsx b/ts/components/menu/ConversationHeaderMenu.tsx
index 7108413f4..36a149ba2 100644
--- a/ts/components/menu/ConversationHeaderMenu.tsx
+++ b/ts/components/menu/ConversationHeaderMenu.tsx
@@ -29,7 +29,8 @@ import {
BlockMenuItem,
ChangeNicknameMenuItem,
ClearNicknameMenuItem,
- DeleteContactMenuItem,
+ DeletePrivateContactMenuItem,
+ DeleteGroupOrCommunityMenuItem,
DeleteMessagesMenuItem,
InviteContactMenuItem,
LeaveGroupMenuItem,
@@ -38,6 +39,7 @@ import {
ShowUserDetailsMenuItem,
UnbanMenuItem,
UpdateGroupNameMenuItem,
+ DeletePrivateConversationMenuItem,
} from './Menu';
import { ContextConversationProvider } from '../leftpane/conversation-list-item/ConvoIdContext';
@@ -71,7 +73,6 @@ export const ConversationHeaderMenu = (props: PropsConversationHeaderMenu) => {
-
@@ -79,7 +80,10 @@ export const ConversationHeaderMenu = (props: PropsConversationHeaderMenu) => {
-
+
+
+
+
diff --git a/ts/components/menu/ConversationListItemContextMenu.tsx b/ts/components/menu/ConversationListItemContextMenu.tsx
index 7704286a5..f91e03c32 100644
--- a/ts/components/menu/ConversationListItemContextMenu.tsx
+++ b/ts/components/menu/ConversationListItemContextMenu.tsx
@@ -17,7 +17,8 @@ import {
CopyMenuItem,
DeclineAndBlockMsgRequestMenuItem,
DeclineMsgRequestMenuItem,
- DeleteContactMenuItem,
+ DeletePrivateContactMenuItem,
+ DeleteGroupOrCommunityMenuItem,
DeleteMessagesMenuItem,
InviteContactMenuItem,
LeaveGroupMenuItem,
@@ -25,6 +26,7 @@ import {
MarkConversationUnreadMenuItem,
ShowUserDetailsMenuItem,
UnbanMenuItem,
+ DeletePrivateConversationMenuItem,
} from './Menu';
export type PropsContextConversationItem = {
@@ -48,7 +50,6 @@ const ConversationListItemContextMenu = (props: PropsContextConversationItem) =>
{/* Read state actions */}
-
{/* Nickname actions */}
@@ -56,7 +57,10 @@ const ConversationListItemContextMenu = (props: PropsContextConversationItem) =>
-
+
+
+
+
diff --git a/ts/components/menu/Menu.tsx b/ts/components/menu/Menu.tsx
index d22ca79c6..5617252ca 100644
--- a/ts/components/menu/Menu.tsx
+++ b/ts/components/menu/Menu.tsx
@@ -35,6 +35,7 @@ import {
unblockConvoById,
} from '../../interactions/conversationInteractions';
import { getConversationController } from '../../session/conversations';
+import { PubKey } from '../../session/types';
import {
changeNickNameModal,
updateConfirmModal,
@@ -44,39 +45,6 @@ import { getIsMessageSection } from '../../state/selectors/section';
import { useSelectedConversationKey } from '../../state/selectors/selectedConversation';
import { SessionButtonColor } from '../basic/SessionButton';
import { useConvoIdFromContext } from '../leftpane/conversation-list-item/ConvoIdContext';
-import { PubKey } from '../../session/types';
-
-function showDeleteContact(
- isGroup: boolean,
- isPublic: boolean,
- isGroupLeft: boolean,
- isKickedFromGroup: boolean,
- isRequest: boolean
-): boolean {
- // you need to have left a closed group first to be able to delete it completely.
- return (!isGroup && !isRequest) || (isGroup && (isGroupLeft || isKickedFromGroup || isPublic));
-}
-
-function showUpdateGroupName(
- weAreAdmin: boolean,
- isKickedFromGroup: boolean,
- left: boolean
-): boolean {
- return !isKickedFromGroup && !left && weAreAdmin;
-}
-
-function showLeaveGroup(
- isKickedFromGroup: boolean,
- left: boolean,
- isGroup: boolean,
- isPublic: boolean
-): boolean {
- return !isKickedFromGroup && !left && isGroup && !isPublic;
-}
-
-function showInviteContact(isPublic: boolean): boolean {
- return isPublic;
-}
/** Menu items standardized */
@@ -84,7 +52,7 @@ export const InviteContactMenuItem = (): JSX.Element | null => {
const convoId = useConvoIdFromContext();
const isPublic = useIsPublic(convoId);
- if (showInviteContact(isPublic)) {
+ if (isPublic) {
return (
- {
@@ -116,24 +84,61 @@ export const MarkConversationUnreadMenuItem = (): JSX.Element | null => {
return null;
};
-export const DeleteContactMenuItem = () => {
+/**
+ * This menu item can be used to completely remove a contact and reset the flags of that conversation.
+ * i.e. after confirmation is made, this contact will be removed from the ContactWrapper, and its blocked and approved state reset.
+ * Note: We keep the entry in the database as the user profile might still be needed for communities/groups where this user.
+ */
+export const DeletePrivateContactMenuItem = () => {
+ const dispatch = useDispatch();
+ const convoId = useConvoIdFromContext();
+ const isPrivate = useIsPrivate(convoId);
+ const isRequest = useIsIncomingRequest(convoId);
+
+ if (isPrivate && !isRequest) {
+ let menuItemText: string;
+
+ menuItemText = window.i18n('editMenuDeleteContact');
+
+ const onClickClose = () => {
+ dispatch(updateConfirmModal(null));
+ };
+
+ const showConfirmationModal = () => {
+ dispatch(
+ updateConfirmModal({
+ title: menuItemText,
+ message: window.i18n('deleteContactConfirmation'),
+ onClickClose,
+ okTheme: SessionButtonColor.Danger,
+ onClickOk: async () => {
+ await getConversationController().deleteContact(convoId, {
+ fromSyncMessage: false,
+ justHidePrivate: false,
+ });
+ },
+ })
+ );
+ };
+
+ return
- {menuItemText}
;
+ }
+ return null;
+};
+
+export const DeleteGroupOrCommunityMenuItem = () => {
const dispatch = useDispatch();
const convoId = useConvoIdFromContext();
const isPublic = useIsPublic(convoId);
const isLeft = useIsLeft(convoId);
const isKickedFromGroup = useIsKickedFromGroup(convoId);
const isPrivate = useIsPrivate(convoId);
- const isRequest = useIsIncomingRequest(convoId);
+ const isGroup = !isPrivate && !isPublic;
- if (showDeleteContact(!isPrivate, isPublic, isLeft, isKickedFromGroup, isRequest)) {
- let menuItemText: string;
- if (isPublic) {
- menuItemText = window.i18n('leaveGroup');
- } else {
- menuItemText = isPrivate
- ? window.i18n('editMenuDeleteContact')
- : window.i18n('editMenuDeleteGroup');
- }
+ // You need to have left a closed group first to be able to delete it completely as there is a leaving message to send first.
+ // A community can just be removed right away.
+ if (isPublic || (isGroup && (isLeft || isKickedFromGroup))) {
+ const menuItemText = isPublic ? window.i18n('leaveGroup') : window.i18n('editMenuDeleteGroup');
const onClickClose = () => {
dispatch(updateConfirmModal(null));
@@ -143,13 +148,13 @@ export const DeleteContactMenuItem = () => {
dispatch(
updateConfirmModal({
title: menuItemText,
- message: isPrivate
- ? window.i18n('deleteContactConfirmation')
- : window.i18n('leaveGroupConfirmation'),
+ message: window.i18n('leaveGroupConfirmation'),
onClickClose,
okTheme: SessionButtonColor.Danger,
onClickOk: async () => {
- await getConversationController().deleteContact(convoId, false);
+ await getConversationController().deleteContact(convoId, {
+ fromSyncMessage: false,
+ });
},
})
);
@@ -167,7 +172,7 @@ export const LeaveGroupMenuItem = () => {
const isKickedFromGroup = useIsKickedFromGroup(convoId);
const isPrivate = useIsPrivate(convoId);
- if (showLeaveGroup(isKickedFromGroup, isLeft, !isPrivate, isPublic)) {
+ if (!isKickedFromGroup && !isLeft && !isPrivate && !isPublic) {
return (
- {
@@ -217,7 +222,7 @@ export const UpdateGroupNameMenuItem = () => {
const isKickedFromGroup = useIsKickedFromGroup(convoId);
const weAreAdmin = useWeAreAdmin(convoId);
- if (showUpdateGroupName(weAreAdmin, isKickedFromGroup, left)) {
+ if (!isKickedFromGroup && !left && weAreAdmin) {
return (
- {
@@ -406,13 +411,17 @@ export const ChangeNicknameMenuItem = () => {
);
};
+/**
+ * This menu is always available and can be used to clear the messages in the local database only.
+ * No messages are sent, no update are made in the wrappers.
+ * Note: Will ask for confirmation before processing.
+ */
export const DeleteMessagesMenuItem = () => {
const convoId = useConvoIdFromContext();
if (!convoId) {
return null;
}
-
return (
- {
@@ -424,6 +433,34 @@ export const DeleteMessagesMenuItem = () => {
);
};
+/**
+ * This menu item can be used to delete a private conversation after confirmation.
+ * It does not reset the flags of that conversation, but just removes the messages locally and hide it from the left pane list.
+ * Note: A dialog is opened to ask for confirmation before processing.
+ */
+export const DeletePrivateConversationMenuItem = () => {
+ const convoId = useConvoIdFromContext();
+ const isRequest = useIsIncomingRequest(convoId);
+ const isPrivate = useIsPrivate(convoId);
+
+ if (!convoId || !isPrivate || isRequest) {
+ return null;
+ }
+
+ return (
+
- {
+ await getConversationController().deleteContact(convoId, {
+ fromSyncMessage: false,
+ justHidePrivate: true,
+ });
+ }}
+ >
+ {window.i18n('deleteConversation')}
+
+ );
+};
+
export const AcceptMsgRequestMenuItem = () => {
const convoId = useConvoIdFromContext();
const isRequest = useIsIncomingRequest(convoId);
diff --git a/ts/interactions/conversationInteractions.ts b/ts/interactions/conversationInteractions.ts
index a2f7ec463..eb2b2da3c 100644
--- a/ts/interactions/conversationInteractions.ts
+++ b/ts/interactions/conversationInteractions.ts
@@ -249,7 +249,9 @@ export function showLeaveGroupByConvoId(conversationId: string) {
title,
message,
onClickOk: async () => {
- await getConversationController().deleteContact(conversation.id, false);
+ await getConversationController().deleteContact(conversation.id, {
+ fromSyncMessage: false,
+ });
onClickClose();
},
onClickClose,
diff --git a/ts/mains/main_renderer.tsx b/ts/mains/main_renderer.tsx
index e1acb2191..b4bac9a83 100644
--- a/ts/mains/main_renderer.tsx
+++ b/ts/mains/main_renderer.tsx
@@ -162,7 +162,7 @@ Storage.onready(async () => {
// Stop background processing
AttachmentDownloads.stop();
// Stop processing incoming messages
- // TODO stop polling opengroupv2 and swarm nodes
+ // TODOLATER stop polling opengroupv2 and swarm nodes
// Shut down the data interface cleanly
await Data.shutdown();
diff --git a/ts/node/sql.ts b/ts/node/sql.ts
index 53de2d8c0..4e3fb2f8a 100644
--- a/ts/node/sql.ts
+++ b/ts/node/sql.ts
@@ -595,7 +595,7 @@ function getUsBlindedInThatServerIfNeeded(
const blindedId = found?.blindedId;
return isString(blindedId) ? blindedId : usNaked;
} catch (e) {
- console.warn('getUsBlindedInThatServerIfNeeded failed with ', e.message);
+ console.error('getUsBlindedInThatServerIfNeeded failed with ', e.message);
}
return usNaked;
@@ -1621,7 +1621,7 @@ const unprocessed: UnprocessedDataNode = {
removeUnprocessed: (id: string): void => {
if (Array.isArray(id)) {
- console.warn('removeUnprocessed only supports single ids at a time');
+ console.error('removeUnprocessed only supports single ids at a time');
throw new Error('removeUnprocessed only supports single ids at a time');
}
assertGlobalInstance()
diff --git a/ts/receiver/configMessage.ts b/ts/receiver/configMessage.ts
index b930acaca..3498a32a1 100644
--- a/ts/receiver/configMessage.ts
+++ b/ts/receiver/configMessage.ts
@@ -1,4 +1,4 @@
-import { compact, isEmpty, isNumber, toNumber } from 'lodash';
+import { compact, difference, isEmpty, isNumber, toNumber } from 'lodash';
import { ConfigDumpData } from '../data/configDump/configDump';
import { Data } from '../data/data';
import { SettingsKey } from '../data/settings-key';
@@ -47,6 +47,9 @@ import { addKeyPairToCacheAndDBIfNeeded, handleNewClosedGroup } from './closedGr
import { HexKeyPair } from './keypairs';
import { queueAllCachedFromSource } from './receiver';
import { EnvelopePlus } from './types';
+import { SessionUtilContact } from '../session/utils/libsession/libsession_utils_contacts';
+import { ContactInfo } from 'libsession_util_nodejs';
+import { getCurrentlySelectedConversationOutsideRedux } from '../state/selectors/conversations';
function groupByVariant(
incomingConfigs: Array>
@@ -136,14 +139,77 @@ async function handleUserProfileUpdate(result: IncomingConfResult): Promise) {
+ const allContactsInDBWhichShouldBeInWrapperIds = getConversationController()
+ .getConversations()
+ .filter(SessionUtilContact.isContactToStoreInWrapper)
+ .map(m => m.id as string);
+
+ const currentlySelectedConversationId = getCurrentlySelectedConversationOutsideRedux();
+ const currentlySelectedConvo = currentlySelectedConversationId
+ ? getConversationController().get(currentlySelectedConversationId)
+ : undefined;
+
+ // we might have some contacts not in the wrapper anymore, so let's clean things up.
+
+ const convoIdsInDbButNotWrapper = difference(
+ allContactsInDBWhichShouldBeInWrapperIds,
+ contactsInWrapper.map(m => m.id)
+ );
+
+ // When starting a conversation with a new user, it is not in the wrapper yet, only when we send the first message.
+ // We do not want to forcefully remove that contact as the user might be typing a message to him.
+ // So let's check if that currently selected conversation should be forcefully closed or not
+ if (
+ currentlySelectedConversationId &&
+ currentlySelectedConvo &&
+ convoIdsInDbButNotWrapper.includes(currentlySelectedConversationId)
+ ) {
+ if (
+ currentlySelectedConvo.isPrivate() &&
+ !currentlySelectedConvo.isApproved() &&
+ !currentlySelectedConvo.didApproveMe()
+ ) {
+ const foundIndex = convoIdsInDbButNotWrapper.findIndex(
+ m => m === currentlySelectedConversationId
+ );
+ if (foundIndex !== -1) {
+ convoIdsInDbButNotWrapper.splice(foundIndex, 1);
+ }
+ }
+ }
+ return convoIdsInDbButNotWrapper;
+}
+
+async function deleteContactsFromDB(contactsToRemove: Array) {
+ window.log.debug('contacts to fully remove after wrapper merge', contactsToRemove);
+ for (let index = 0; index < contactsToRemove.length; index++) {
+ const contactToRemove = contactsToRemove[index];
+ try {
+ await getConversationController().deleteContact(contactToRemove, {
+ fromSyncMessage: true,
+ justHidePrivate: false,
+ });
+ } catch (e) {
+ window.log.warn(
+ `after merge: deleteContactsFromDB ${contactToRemove} failed with `,
+ e.message
+ );
+ }
+ }
+}
+
// tslint:disable-next-line: cyclomatic-complexity
async function handleContactsUpdate(result: IncomingConfResult): Promise {
const us = UserUtils.getOurPubKeyStrFromCache();
- const allContacts = await ContactsWrapperActions.getAll();
+ const allContactsInWrapper = await ContactsWrapperActions.getAll();
+ const contactsToRemoveFromDB = getContactsToRemoveFromDB(allContactsInWrapper);
+ await deleteContactsFromDB(contactsToRemoveFromDB);
- for (let index = 0; index < allContacts.length; index++) {
- const wrapperConvo = allContacts[index];
+ // create new contact conversation here, and update their state with what is part of the wrapper
+ for (let index = 0; index < allContactsInWrapper.length; index++) {
+ const wrapperConvo = allContactsInWrapper[index];
if (wrapperConvo.id === us) {
// our profile update comes from our userProfile, not from the contacts wrapper.
@@ -259,8 +325,10 @@ async function handleCommunitiesUpdate() {
for (let index = 0; index < communitiesToLeaveInDB.length; index++) {
const toLeave = communitiesToLeaveInDB[index];
- console.warn('leaving community with convoId ', toLeave.id);
- await getConversationController().deleteContact(toLeave.id, true);
+ window.log.info('leaving community with convoId ', toLeave.id);
+ await getConversationController().deleteContact(toLeave.id, {
+ fromSyncMessage: true,
+ });
}
// this call can take quite a long time and should not cause issues to not be awaited
@@ -328,7 +396,10 @@ async function handleLegacyGroupUpdate(latestEnvelopeTimestamp: number) {
for (let index = 0; index < legacyGroupsToLeaveInDB.length; index++) {
const toLeave = legacyGroupsToLeaveInDB[index];
- console.warn('leaving legacy group from configuration sync message with convoId ', toLeave.id);
+ window.log.info(
+ 'leaving legacy group from configuration sync message with convoId ',
+ toLeave.id
+ );
const toLeaveFromDb = getConversationController().get(toLeave.id);
// if we were kicked from that group, leave it as is until the user manually deletes it
@@ -336,13 +407,15 @@ async function handleLegacyGroupUpdate(latestEnvelopeTimestamp: number) {
if (!toLeaveFromDb?.get('isKickedFromGroup')) {
window.log.debug(`we were kicked from ${toLeave.id} so we keep it until manually deleted`);
- await getConversationController().deleteContact(toLeave.id, true);
+ await getConversationController().deleteContact(toLeave.id, {
+ fromSyncMessage: true,
+ });
}
}
for (let index = 0; index < legacyGroupsToJoinInDB.length; index++) {
const toJoin = legacyGroupsToJoinInDB[index];
- console.warn(
+ window.log.info(
'joining legacy group from configuration sync message with convoId ',
toJoin.pubkeyHex
);
diff --git a/ts/session/apis/open_group_api/opengroupV2/JoinOpenGroupV2.ts b/ts/session/apis/open_group_api/opengroupV2/JoinOpenGroupV2.ts
index a1d12453e..e2ad61955 100644
--- a/ts/session/apis/open_group_api/opengroupV2/JoinOpenGroupV2.ts
+++ b/ts/session/apis/open_group_api/opengroupV2/JoinOpenGroupV2.ts
@@ -82,7 +82,9 @@ async function joinOpenGroupV2(
// we already have a convo associated with it. Remove everything related to it so we start fresh
window?.log?.warn('leaving before rejoining open group v2 room', conversationId);
- await getConversationController().deleteContact(conversationId, true);
+ await getConversationController().deleteContact(conversationId, {
+ fromSyncMessage: true,
+ });
}
// Try to connect to server
diff --git a/ts/session/apis/open_group_api/opengroupV2/OpenGroupManagerV2.ts b/ts/session/apis/open_group_api/opengroupV2/OpenGroupManagerV2.ts
index a6ae4ac66..868e0bbb8 100644
--- a/ts/session/apis/open_group_api/opengroupV2/OpenGroupManagerV2.ts
+++ b/ts/session/apis/open_group_api/opengroupV2/OpenGroupManagerV2.ts
@@ -152,7 +152,9 @@ export class OpenGroupManagerV2 {
await OpenGroupData.removeV2OpenGroupRoom(roomConvoId);
getOpenGroupManager().removeRoomFromPolledRooms(infos);
- await getConversationController().deleteContact(roomConvoId, false);
+ await getConversationController().deleteContact(roomConvoId, {
+ fromSyncMessage: false,
+ });
}
} catch (e) {
window?.log?.warn('cleanup roomInfos error', e);
diff --git a/ts/session/apis/snode_api/batchRequest.ts b/ts/session/apis/snode_api/batchRequest.ts
index baba864af..01affdf87 100644
--- a/ts/session/apis/snode_api/batchRequest.ts
+++ b/ts/session/apis/snode_api/batchRequest.ts
@@ -63,7 +63,7 @@ export async function doSnodeBatchRequest(
*/
function decodeBatchRequest(snodeResponse: SnodeResponse): NotEmptyArrayOfBatchResults {
try {
- // console.warn('decodeBatch: ', snodeResponse);
+ // console.error('decodeBatch: ', snodeResponse);
if (snodeResponse.status !== 200) {
throw new Error(`decodeBatchRequest invalid status code: ${snodeResponse.status}`);
}
diff --git a/ts/session/conversations/ConversationController.ts b/ts/session/conversations/ConversationController.ts
index 7849443b4..1012e5ee4 100644
--- a/ts/session/conversations/ConversationController.ts
+++ b/ts/session/conversations/ConversationController.ts
@@ -24,7 +24,7 @@ import { getSwarmPollingInstance } from '../apis/snode_api';
import { SnodeNamespaces } from '../apis/snode_api/namespaces';
import { ClosedGroupMemberLeftMessage } from '../messages/outgoing/controlMessage/group/ClosedGroupMemberLeftMessage';
import { UserUtils } from '../utils';
-import { isEmpty } from 'lodash';
+import { isEmpty, isNil } from 'lodash';
let instance: ConversationController | null;
@@ -200,9 +200,12 @@ export class ConversationController {
await conversation.commit();
}
- public async deleteContact(id: string, fromSyncMessage: boolean) {
+ public async deleteContact(
+ id: string,
+ options: { fromSyncMessage: boolean; justHidePrivate?: boolean }
+ ) {
if (!this._initialFetchComplete) {
- throw new Error('getConversationController().deleteContact() needs complete initial fetch');
+ throw new Error('getConversationController.deleteContact needs complete initial fetch');
}
window.log.info(`deleteContact with ${id}`);
@@ -227,18 +230,31 @@ export class ConversationController {
switch (convoType) {
case '1o1':
// if this conversation is a private conversation it's in fact a `contact` for desktop.
- // we just set the hidden field to true
- // so the conversation still exists (needed for that user's profile in groups) but is not shown on the list of conversation.
- // We also keep the messages for now, as turning a contact as hidden might just be a temporary thing
- window.log.info(`deleteContact isPrivate, marking as hidden: ${id}`);
- conversation.set({
- priority: CONVERSATION_PRIORITIES.hidden,
- });
- // we currently do not wish to reset the approved/approvedMe state when marking a private conversation as hidden
- // await conversation.setIsApproved(false, false);
- await conversation.commit(); // this updates the wrappers content to reflect the hidden state
-
- // We don't remove entries from the contacts wrapper, so better keep corresponding convo volatile info for now (it will be pruned if needed)
+
+ if (options.justHidePrivate || isNil(options.justHidePrivate) || conversation.isMe()) {
+ // we just set the hidden field to true
+ // so the conversation still exists (needed for that user's profile in groups) but is not shown on the list of conversation.
+ // We also keep the messages for now, as turning a contact as hidden might just be a temporary thing
+ window.log.info(`deleteContact isPrivate, marking as hidden: ${id}`);
+ conversation.set({
+ priority: CONVERSATION_PRIORITIES.hidden,
+ });
+ // We don't remove entries from the contacts wrapper, so better keep corresponding convo volatile info for now (it will be pruned if needed)
+ await conversation.commit(); // this updates the wrappers content to reflect the hidden state
+ } else {
+ window.log.info(`deleteContact isPrivate, reset fields and removing from wrapper: ${id}`);
+
+ await conversation.setIsApproved(false, false);
+ await conversation.setDidApproveMe(false, false);
+ conversation.set('active_at', 0);
+ await BlockedNumberController.unblockAll([conversation.id]);
+ await conversation.commit(); // first commit to DB so the DB knows about the changes
+ if (SessionUtilContact.isContactToStoreInWrapper(conversation)) {
+ window.log.warn('isContactToStoreInWrapper still true for ', conversation.attributes);
+ }
+ await SessionUtilContact.removeContactFromWrapper(conversation.id); // then remove the entry alltogether from the wrapper
+ }
+
break;
case 'Community':
window?.log?.info('leaving open group v2', conversation.id);
@@ -280,14 +296,14 @@ export class ConversationController {
break;
case 'LegacyGroup':
window.log.info(`deleteContact ClosedGroup case: ${conversation.id}`);
- await leaveClosedGroup(conversation.id, fromSyncMessage); // this removes the data from the group and convo volatile info
+ await leaveClosedGroup(conversation.id, options.fromSyncMessage); // this removes the data from the group and convo volatile info
await this.cleanUpGroupConversation(conversation.id);
break;
default:
assertUnreachable(convoType, `deleteContact: convoType ${convoType} not handled`);
}
- if (!fromSyncMessage) {
+ if (!options.fromSyncMessage) {
await ConfigurationSync.queueNewJobIfNeeded();
}
}
diff --git a/ts/session/crypto/index.ts b/ts/session/crypto/index.ts
index 1c8640b59..c2d63d5a3 100644
--- a/ts/session/crypto/index.ts
+++ b/ts/session/crypto/index.ts
@@ -68,7 +68,7 @@ export async function generateGroupV3Keypair() {
preprendedPubkey.set(publicKey, 1);
preprendedPubkey[0] = 3;
- console.warn(`generateGroupV3Keypair: pubkey${toHex(preprendedPubkey)}`);
+ // console.warn(`generateGroupV3Keypair: pubkey${toHex(preprendedPubkey)}`);
return { pubkey: toHex(preprendedPubkey), privateKey: toHex(ed25519KeyPair.privateKey) };
}
diff --git a/ts/session/messages/outgoing/controlMessage/ExpirationTimerUpdateMessage.ts b/ts/session/messages/outgoing/controlMessage/ExpirationTimerUpdateMessage.ts
index 05ad7d874..d99a31ab0 100644
--- a/ts/session/messages/outgoing/controlMessage/ExpirationTimerUpdateMessage.ts
+++ b/ts/session/messages/outgoing/controlMessage/ExpirationTimerUpdateMessage.ts
@@ -29,7 +29,7 @@ export class ExpirationTimerUpdateMessage extends DataMessage {
data.flags = SignalService.DataMessage.Flags.EXPIRATION_TIMER_UPDATE;
- // TODO we shouldn't need this once android recieving refactor is done.
+ // TODOLATER we won't need this once legacy groups are not supported anymore
// the envelope stores the groupId for a closed group already.
if (this.groupId) {
const groupMessage = new SignalService.GroupContext();
diff --git a/ts/session/sending/MessageQueue.ts b/ts/session/sending/MessageQueue.ts
index 99eaf4f36..b7401fb78 100644
--- a/ts/session/sending/MessageQueue.ts
+++ b/ts/session/sending/MessageQueue.ts
@@ -331,7 +331,7 @@ export class MessageQueue {
// or a message with a syncTarget set.
if (MessageSender.isSyncMessage(message)) {
- window?.log?.warn('OutgoingMessageQueue: Processing sync message');
+ window?.log?.info('OutgoingMessageQueue: Processing sync message');
isSyncMessage = true;
} else {
window?.log?.warn('Dropping message in process() to be sent to ourself');
diff --git a/ts/session/utils/job_runners/jobs/ConfigurationSyncJob.ts b/ts/session/utils/job_runners/jobs/ConfigurationSyncJob.ts
index d2a64a26c..48d77bfa3 100644
--- a/ts/session/utils/job_runners/jobs/ConfigurationSyncJob.ts
+++ b/ts/session/utils/job_runners/jobs/ConfigurationSyncJob.ts
@@ -287,11 +287,12 @@ async function queueNewJobIfNeeded() {
!lastRunConfigSyncJobTimestamp ||
lastRunConfigSyncJobTimestamp < Date.now() - defaultMsBetweenRetries
) {
+ window.log.debug('Scheduling ConfSyncJob: ASAP');
+ // we postpone by 1000ms to make sure whoever is adding this job is done with what is needs to do first
// this call will make sure that there is only one configuration sync job at all times
await runners.configurationSyncRunner.addJob(
new ConfigurationSyncJob({ nextAttemptTimestamp: Date.now() + 1000 })
);
- window.log.debug('Scheduling ConfSyncJob: ASAP'); // we postpone by 1000ms to make sure whoever is adding this job is done with what is needs to do first
} else {
// if we did run at t=100, and it is currently t=110, the difference is 10
const diff = Math.max(Date.now() - lastRunConfigSyncJobTimestamp, 0);
diff --git a/ts/session/utils/libsession/libsession_utils.ts b/ts/session/utils/libsession/libsession_utils.ts
index 79cb65977..153a46e72 100644
--- a/ts/session/utils/libsession/libsession_utils.ts
+++ b/ts/session/utils/libsession/libsession_utils.ts
@@ -139,7 +139,7 @@ async function pendingChangesForPubkey(pubkey: string): Promise Boolean(convo?.isInitialFetchingInProgress)
);
+
+export function getCurrentlySelectedConversationOutsideRedux() {
+ return window?.inboxStore?.getState().conversations.selectedConversation as string | undefined;
+}
diff --git a/ts/test/automation/password.spec.ts b/ts/test/automation/password.spec.ts
index 17c9cf177..b2063bf3a 100644
--- a/ts/test/automation/password.spec.ts
+++ b/ts/test/automation/password.spec.ts
@@ -11,6 +11,7 @@ import {
waitForTestIdWithText,
} from './utils';
let window: Page | undefined;
+// tslint:disable: no-console
test.beforeEach(beforeAllClean);
@@ -61,7 +62,7 @@ test.describe('Password checks', () => {
// Change password
await clickOnTestIdWithText(window, 'change-password-settings-button', 'Change Password');
- console.warn('clicked Change Password');
+ console.info('clicked Change Password');
// Enter old password
await typeIntoInput(window, 'password-input', testPassword);
// Enter new password
diff --git a/ts/test/session/unit/reactions/ReactionMessage_test.ts b/ts/test/session/unit/reactions/ReactionMessage_test.ts
index 2a53368d9..8961626d1 100644
--- a/ts/test/session/unit/reactions/ReactionMessage_test.ts
+++ b/ts/test/session/unit/reactions/ReactionMessage_test.ts
@@ -106,7 +106,7 @@ describe('ReactionMessage', () => {
expect(reaction, 'no reaction should be returned since we are over the rate limit').to.be
.undefined;
- clock = useFakeTimers(Date.now());
+ clock = useFakeTimers({ now: Date.now(), shouldAdvanceTime: true });
// Wait a miniute for the rate limit to clear
clock.tick(1 * 60 * 1000);
diff --git a/ts/test/session/unit/sending/MessageSender_test.ts b/ts/test/session/unit/sending/MessageSender_test.ts
index 2245a1f3a..4626fae4d 100644
--- a/ts/test/session/unit/sending/MessageSender_test.ts
+++ b/ts/test/session/unit/sending/MessageSender_test.ts
@@ -80,13 +80,10 @@ describe('MessageSender', () => {
});
it('should only retry the specified amount of times before throwing', async () => {
- // const clock = sinon.useFakeTimers();
-
sessionMessageAPISendStub.throws(new Error('API error'));
const attempts = 2;
const promise = MessageSender.send(rawMessage, attempts, 10);
await expect(promise).is.rejectedWith('API error');
- // clock.restore();
expect(sessionMessageAPISendStub.callCount).to.equal(attempts);
});
diff --git a/ts/test/session/unit/swarm_polling/SwarmPolling_test.ts b/ts/test/session/unit/swarm_polling/SwarmPolling_test.ts
index 6060d4ae6..de03273fa 100644
--- a/ts/test/session/unit/swarm_polling/SwarmPolling_test.ts
+++ b/ts/test/session/unit/swarm_polling/SwarmPolling_test.ts
@@ -64,7 +64,7 @@ describe('SwarmPolling', () => {
swarmPolling.resetSwarmPolling();
pollOnceForKeySpy = Sinon.spy(swarmPolling, 'pollOnceForKey');
- clock = sinon.useFakeTimers(Date.now());
+ clock = sinon.useFakeTimers({ now: Date.now(), shouldAdvanceTime: true });
});
afterEach(() => {
@@ -321,14 +321,16 @@ describe('SwarmPolling', () => {
const groupConvoPubkey = PubKey.cast(convo.id as string);
swarmPolling.addGroupId(groupConvoPubkey);
await swarmPolling.start(true);
+ expect(pollOnceForKeySpy.callCount).to.eq(2);
+ expect(pollOnceForKeySpy.firstCall.args).to.deep.eq([ourPubkey, false, [0]]);
+ expect(pollOnceForKeySpy.secondCall.args).to.deep.eq([groupConvoPubkey, true, [-10]]);
clock.tick(9000);
// no need to do that as the tick will trigger a call in all cases after 5 secs await swarmPolling.pollForAllKeys();
/** this is not easy to explain, but
- * - during the swarmPolling.start, we get two calls to pollOnceForKeySpy (one for our id and one for group od)
+ * - during the swarmPolling.start, we get two calls to pollOnceForKeySpy (one for our id and one for group id)
* - the clock ticks 9sec, and another call of pollOnceForKeySpy get started, but as we do not await them, this test fails.
* the only fix is to restore the clock and force the a small sleep to let the thing run in bg
*/
- clock.restore();
await sleepFor(10);
expect(pollOnceForKeySpy.callCount).to.eq(4);
@@ -360,7 +362,6 @@ describe('SwarmPolling', () => {
* - the clock ticks 9sec, and another call of pollOnceForKeySpy get started, but as we do not await them, this test fails.
* the only fix is to restore the clock and force the a small sleep to let the thing run in bg
*/
- clock.restore();
await sleepFor(10);
// we should have two more calls here, so 4 total.
expect(pollOnceForKeySpy.callCount).to.eq(4);
@@ -386,6 +387,7 @@ describe('SwarmPolling', () => {
convo.set('active_at', Date.now() - 7 * 24 * 3600 * 1000 - 3600 * 1000);
clock.tick(1 * 60 * 1000);
+ await sleepFor(10);
// we should have only one more call here, the one for our direct pubkey fetch
expect(pollOnceForKeySpy.callCount).to.eq(3);
diff --git a/ts/test/session/unit/utils/job_runner/JobRunner_test.ts b/ts/test/session/unit/utils/job_runner/JobRunner_test.ts
index f8a024950..675fb04d1 100644
--- a/ts/test/session/unit/utils/job_runner/JobRunner_test.ts
+++ b/ts/test/session/unit/utils/job_runner/JobRunner_test.ts
@@ -11,6 +11,7 @@ import {
import { sleepFor } from '../../../../../session/utils/Promise';
import { stubData } from '../../../../test-utils/utils';
import { TestUtils } from '../../../../test-utils';
+// tslint:disable: no-console
function getFakeSleepForJob(timestamp: number): FakeSleepForJob {
const job = new FakeSleepForJob({
@@ -200,12 +201,12 @@ describe('JobRunner', () => {
expect(runnerMulti.getJobList()).to.deep.eq([job.serializeJob(), job2.serializeJob()]);
expect(runnerMulti.getCurrentJobIdentifier()).to.be.equal(job.persistedData.identifier);
- console.warn(
+ console.info(
'runnerMulti.getJobList() initial',
runnerMulti.getJobList().map(m => m.identifier),
Date.now()
);
- console.warn('=========== awaiting first job ==========');
+ console.info('=========== awaiting first job ==========');
// each job takes 5s to finish, so let's tick once the first one should be done
clock.tick(5000);
@@ -214,10 +215,10 @@ describe('JobRunner', () => {
expect(awaited).to.eq('await');
await sleepFor(10);
- console.warn('=========== awaited first job ==========');
+ console.info('=========== awaited first job ==========');
expect(runnerMulti.getCurrentJobIdentifier()).to.be.equal(job2.persistedData.identifier);
- console.warn('=========== awaiting second job ==========');
+ console.info('=========== awaiting second job ==========');
clock.tick(5000);
@@ -225,7 +226,7 @@ describe('JobRunner', () => {
expect(awaited).to.eq('await');
await sleepFor(10); // those sleep for is just to let the runner the time to finish writing the tests to the DB and exit the handling of the previous test
- console.warn('=========== awaited second job ==========');
+ console.info('=========== awaited second job ==========');
expect(runnerMulti.getCurrentJobIdentifier()).to.eq(null);
@@ -245,27 +246,27 @@ describe('JobRunner', () => {
expect(runnerMulti.getCurrentJobIdentifier()).to.be.equal(job.persistedData.identifier);
clock.tick(5000);
- console.warn('=========== awaiting first job ==========');
+ console.info('=========== awaiting first job ==========');
await runnerMulti.waitCurrentJob();
// just give some time for the runnerMulti to pick up a new job
await sleepFor(10);
expect(runnerMulti.getJobList()).to.deep.eq([]);
expect(runnerMulti.getCurrentJobIdentifier()).to.be.equal(null);
- console.warn('=========== awaited first job ==========');
+ console.info('=========== awaited first job ==========');
// the first job should already be finished now
result = await runnerMulti.addJob(job2);
expect(result).to.eq('job_started');
expect(runnerMulti.getJobList()).to.deep.eq([job2.serializeJob()]);
- console.warn('=========== awaiting second job ==========');
+ console.info('=========== awaiting second job ==========');
// each job takes 5s to finish, so let's tick once the first one should be done
clock.tick(5010);
await runnerMulti.waitCurrentJob();
await sleepFor(10);
- console.warn('=========== awaited second job ==========');
+ console.info('=========== awaited second job ==========');
expect(runnerMulti.getJobList()).to.deep.eq([]);
});
diff --git a/ts/types/LocalizerKeys.ts b/ts/types/LocalizerKeys.ts
index a180e1659..1c7f7a0e0 100644
--- a/ts/types/LocalizerKeys.ts
+++ b/ts/types/LocalizerKeys.ts
@@ -100,6 +100,7 @@ export type LocalizerKeys =
| 'deleteMessagesQuestion'
| 'deleteMessageQuestion'
| 'deleteMessages'
+ | 'deleteConversation'
| 'deleted'
| 'messageDeletedPlaceholder'
| 'from'