From 1c50aacc3407b84847fab54040eadf60a2ca1703 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Thu, 16 Mar 2023 14:31:52 +1100 Subject: [PATCH] chore: remove groupModerators sogs from the db, store in redux only --- .../message/message-content/MessageAvatar.tsx | 2 +- .../leftpane/LeftPaneMessageSection.tsx | 2 +- .../leftpane/LeftPaneSectionHeader.tsx | 15 ++-- .../ConversationListItem.tsx | 52 ++++++------ .../conversation-list-item/MessageItem.tsx | 6 +- .../overlay/OverlayMessageRequest.tsx | 8 +- ts/components/search/SearchResults.tsx | 2 - ts/hooks/useParamSelector.ts | 16 +++- ts/interactions/conversationInteractions.ts | 6 +- .../conversations/unsendingInteractions.ts | 4 +- ts/models/conversation.ts | 85 ++++++------------- ts/models/conversationAttributes.ts | 36 ++++---- ts/models/message.ts | 8 +- ts/node/database_utility.ts | 9 -- ts/node/migration/sessionMigrations.ts | 3 + ts/node/sql.ts | 6 -- ts/receiver/closedGroups.ts | 2 +- ts/receiver/configMessage.ts | 2 +- ts/receiver/contentMessage.ts | 2 +- ts/session/apis/snode_api/swarmPolling.ts | 5 +- .../conversations/ConversationController.ts | 8 -- ts/session/group/closed-group.ts | 14 +-- ts/session/utils/Groups.ts | 4 +- .../libsession_utils_convo_info_volatile.ts | 2 +- ts/session/utils/sync/syncUtils.ts | 2 +- ts/state/ducks/conversations.ts | 1 - ts/state/ducks/sogsRoomInfo.tsx | 48 ++++++++++- ts/state/selectors/conversations.ts | 14 +-- ts/state/selectors/section.ts | 7 ++ ts/state/selectors/sogsRoomInfo.ts | 26 +++++- .../unit/models/ConversationModels_test.ts | 34 -------- .../models/formatRowOfConversation_test.ts | 21 ----- .../unit/selectors/conversations_test.ts | 12 --- ts/types/MessageAttachment.ts | 2 +- 34 files changed, 205 insertions(+), 261 deletions(-) diff --git a/ts/components/conversation/message/message-content/MessageAvatar.tsx b/ts/components/conversation/message/message-content/MessageAvatar.tsx index 501e8b5e9..62728c906 100644 --- a/ts/components/conversation/message/message-content/MessageAvatar.tsx +++ b/ts/components/conversation/message/message-content/MessageAvatar.tsx @@ -81,7 +81,7 @@ export const MessageAvatar = (props: Props) => { } if (isPublic && !isTypingEnabled) { - window.log.info('onMessageAvatarClick: no typing enabled. Dropping...'); + window.log.info('onMessageAvatarClick: typing is disabled...'); return; } diff --git a/ts/components/leftpane/LeftPaneMessageSection.tsx b/ts/components/leftpane/LeftPaneMessageSection.tsx index a04cb402e..1d0ac4e49 100644 --- a/ts/components/leftpane/LeftPaneMessageSection.tsx +++ b/ts/components/leftpane/LeftPaneMessageSection.tsx @@ -104,7 +104,7 @@ export class LeftPaneMessageSection extends React.Component { const length = conversations.length; // Note: conversations is not a known prop for List, but it is required to ensure that - // it re-renders when our conversation data changes. Otherwise it would just render + // it re-renders when our conversations data changes. Otherwise it would just render // on startup and scroll. return ( diff --git a/ts/components/leftpane/LeftPaneSectionHeader.tsx b/ts/components/leftpane/LeftPaneSectionHeader.tsx index 4a9f2b1c9..34789c14c 100644 --- a/ts/components/leftpane/LeftPaneSectionHeader.tsx +++ b/ts/components/leftpane/LeftPaneSectionHeader.tsx @@ -1,14 +1,14 @@ import React from 'react'; -import styled from 'styled-components'; import { useDispatch, useSelector } from 'react-redux'; +import styled from 'styled-components'; +import { recoveryPhraseModal } from '../../state/ducks/modalDialog'; +import { SectionType } from '../../state/ducks/section'; import { disableRecoveryPhrasePrompt } from '../../state/ducks/userConfig'; +import { getFocusedSection, getIsMessageRequestOverlayShown } from '../../state/selectors/section'; import { getShowRecoveryPhrasePrompt } from '../../state/selectors/userConfig'; -import { recoveryPhraseModal } from '../../state/ducks/modalDialog'; +import { isSignWithRecoveryPhrase } from '../../util/storage'; import { Flex } from '../basic/Flex'; -import { getFocusedSection, getOverlayMode } from '../../state/selectors/section'; -import { SectionType } from '../../state/ducks/section'; import { SessionButton } from '../basic/SessionButton'; -import { isSignWithRecoveryPhrase } from '../../util/storage'; import { MenuButton } from '../button/MenuButton'; const SectionTitle = styled.h1` @@ -110,19 +110,18 @@ export const LeftPaneBanner = () => { export const LeftPaneSectionHeader = () => { const showRecoveryPhrasePrompt = useSelector(getShowRecoveryPhrasePrompt); const focusedSection = useSelector(getFocusedSection); - const overlayMode = useSelector(getOverlayMode); + const isMessageRequestOverlayShown = useSelector(getIsMessageRequestOverlayShown); let label: string | undefined; const isMessageSection = focusedSection === SectionType.Message; - const isMessageRequestOverlay = overlayMode && overlayMode === 'message-requests'; switch (focusedSection) { case SectionType.Settings: label = window.i18n('settingsHeader'); break; case SectionType.Message: - label = isMessageRequestOverlay + label = isMessageRequestOverlayShown ? window.i18n('messageRequests') : window.i18n('messagesHeader'); break; diff --git a/ts/components/leftpane/conversation-list-item/ConversationListItem.tsx b/ts/components/leftpane/conversation-list-item/ConversationListItem.tsx index 3b8205a40..af4889925 100644 --- a/ts/components/leftpane/conversation-list-item/ConversationListItem.tsx +++ b/ts/components/leftpane/conversation-list-item/ConversationListItem.tsx @@ -1,32 +1,35 @@ -import React, { useCallback, useContext } from 'react'; import classNames from 'classnames'; +import React, { useCallback, useContext } from 'react'; import { contextMenu } from 'react-contexify'; import { Avatar, AvatarSize } from '../../avatar/Avatar'; import { createPortal } from 'react-dom'; +import { useDispatch } from 'react-redux'; import { openConversationWithMessages, ReduxConversationType, } from '../../../state/ducks/conversations'; -import { useDispatch } from 'react-redux'; import { updateUserDetailsModal } from '../../../state/ducks/modalDialog'; +import _ from 'lodash'; +import { useSelector } from 'react-redux'; import { useAvatarPath, useConversationUsername, + useHasUnread, + useIsBlocked, useIsPrivate, + useIsSelectedConversation, + useMentionedUs, } from '../../../hooks/useParamSelector'; +import { isSearching } from '../../../state/selectors/search'; import { MemoConversationListItemContextMenu } from '../../menu/ConversationListItemContextMenu'; import { ConversationListItemHeaderItem } from './HeaderItem'; import { MessageItem } from './MessageItem'; -import _ from 'lodash'; // tslint:disable-next-line: no-empty-interface -export type ConversationListItemProps = Pick< - ReduxConversationType, - 'id' | 'isSelected' | 'isBlocked' | 'mentionedUs' | 'unreadCount' | 'displayNameInProfile' ->; +export type ConversationListItemProps = Pick; /** * This React context is used to share deeply in the tree of the ConversationListItem what is the ID we are currently rendering. @@ -36,7 +39,6 @@ export const ContextConversationId = React.createContext(''); type PropsHousekeeping = { style?: Object; - isMessageRequest?: boolean; }; // tslint:disable: use-simple-attributes @@ -74,19 +76,23 @@ const AvatarItem = () => { ); }; -// tslint:disable: max-func-body-length const ConversationListItem = (props: Props) => { - const { - unreadCount, - id: conversationId, - isSelected, - isBlocked, - style, - mentionedUs, - isMessageRequest, - } = props; + const { id: conversationId, style } = props; const key = `conversation-item-${conversationId}`; + const hasUnread = useHasUnread(conversationId); + + let hasUnreadMentionedUs = useMentionedUs(conversationId); + let isBlocked = useIsBlocked(conversationId); + const isSearch = useSelector(isSearching); + const isSelectedConvo = useIsSelectedConversation(conversationId); + + if (isSearch) { + // force isBlocked and hasUnreadMentionedUs to be false, we just want to display the row without any special style when showing search results + hasUnreadMentionedUs = false; + isBlocked = false; + } + const triggerId = `${key}-ctxmenu`; const openConvo = useCallback( @@ -118,18 +124,16 @@ const ConversationListItem = (props: Props) => { style={style} className={classNames( 'module-conversation-list-item', - unreadCount && unreadCount > 0 ? 'module-conversation-list-item--has-unread' : null, - unreadCount && unreadCount > 0 && mentionedUs - ? 'module-conversation-list-item--mentioned-us' - : null, - isSelected ? 'module-conversation-list-item--is-selected' : null, + hasUnread ? 'module-conversation-list-item--has-unread' : null, + hasUnreadMentionedUs ? 'module-conversation-list-item--mentioned-us' : null, + isSelectedConvo ? 'module-conversation-list-item--is-selected' : null, isBlocked ? 'module-conversation-list-item--is-blocked' : null )} >
- +
diff --git a/ts/components/leftpane/conversation-list-item/MessageItem.tsx b/ts/components/leftpane/conversation-list-item/MessageItem.tsx index f4905910c..ba50e0147 100644 --- a/ts/components/leftpane/conversation-list-item/MessageItem.tsx +++ b/ts/components/leftpane/conversation-list-item/MessageItem.tsx @@ -13,6 +13,7 @@ import { TypingAnimation } from '../../conversation/TypingAnimation'; import { ContextConversationId } from './ConversationListItem'; import { useSelector } from 'react-redux'; import { isSearching } from '../../../state/selectors/search'; +import { getIsMessageRequestOverlayShown } from '../../../state/selectors/section'; function useLastMessageFromConvo(convoId: string) { const convoProps = useConversationPropsById(convoId); @@ -22,13 +23,14 @@ function useLastMessageFromConvo(convoId: string) { return convoProps.lastMessage; } -export const MessageItem = (props: { isMessageRequest: boolean }) => { +export const MessageItem = () => { const conversationId = useContext(ContextConversationId); const lastMessage = useLastMessageFromConvo(conversationId); const isGroup = !useIsPrivate(conversationId); const hasUnread = useHasUnread(conversationId); const isConvoTyping = useIsTyping(conversationId); + const isMessageRequest = useSelector(getIsMessageRequestOverlayShown); const isSearchingMode = useSelector(isSearching); @@ -55,7 +57,7 @@ export const MessageItem = (props: { isMessageRequest: boolean }) => { )} - {!isSearchingMode && lastMessage && lastMessage.status && !props.isMessageRequest ? ( + {!isSearchingMode && lastMessage && lastMessage.status && !isMessageRequest ? ( ) : null} diff --git a/ts/components/leftpane/overlay/OverlayMessageRequest.tsx b/ts/components/leftpane/overlay/OverlayMessageRequest.tsx index 95793b669..36fe4e00f 100644 --- a/ts/components/leftpane/overlay/OverlayMessageRequest.tsx +++ b/ts/components/leftpane/overlay/OverlayMessageRequest.tsx @@ -41,13 +41,7 @@ const MessageRequestList = () => { return ( {conversationRequests.map(conversation => { - return ( - - ); + return ; })} ); diff --git a/ts/components/search/SearchResults.tsx b/ts/components/search/SearchResults.tsx index 9e6484e4b..be7aea4a3 100644 --- a/ts/components/search/SearchResults.tsx +++ b/ts/components/search/SearchResults.tsx @@ -54,8 +54,6 @@ export const SearchResults = (props: SearchResultsProps) => { {contactsAndGroups.map(contactOrGroup => ( ))} diff --git a/ts/hooks/useParamSelector.ts b/ts/hooks/useParamSelector.ts index ef57f7ff5..33b608a05 100644 --- a/ts/hooks/useParamSelector.ts +++ b/ts/hooks/useParamSelector.ts @@ -1,10 +1,13 @@ -import { isEmpty, pick } from 'lodash'; +import { isEmpty, isNil, pick } from 'lodash'; import { useSelector } from 'react-redux'; import { ConversationModel } from '../models/conversation'; import { PubKey } from '../session/types'; import { UserUtils } from '../session/utils'; import { StateType } from '../state/reducer'; -import { getMessageReactsProps } from '../state/selectors/conversations'; +import { + getMessageReactsProps, + getSelectedConversationKey, +} from '../state/selectors/conversations'; export function useAvatarPath(convoId: string | undefined) { const convoProps = useConversationPropsById(convoId); @@ -202,13 +205,13 @@ export function useIsForcedUnreadWithoutUnreadMsg(conversationId?: string): bool return convoProps?.isMarkedUnread || false; } -function useMentionedUsNoUnread(conversationId?: string) { +function useMentionedUsUnread(conversationId?: string) { const convoProps = useConversationPropsById(conversationId); return convoProps?.mentionedUs || false; } export function useMentionedUs(conversationId?: string): boolean { - const hasMentionedUs = useMentionedUsNoUnread(conversationId); + const hasMentionedUs = useMentionedUsUnread(conversationId); const hasUnread = useHasUnread(conversationId); return hasMentionedUs && hasUnread; @@ -217,3 +220,8 @@ export function useMentionedUs(conversationId?: string): boolean { export function useIsTyping(conversationId?: string): boolean { return useConversationPropsById(conversationId)?.isTyping || false; } + +export function useIsSelectedConversation(conversation?: string): boolean { + const selectedConvo = useSelector(getSelectedConversationKey); + return !isNil(selectedConvo) && !isNil(conversation) && selectedConvo === conversation; +} diff --git a/ts/interactions/conversationInteractions.ts b/ts/interactions/conversationInteractions.ts index 1f8e13d02..98ec27e62 100644 --- a/ts/interactions/conversationInteractions.ts +++ b/ts/interactions/conversationInteractions.ts @@ -157,7 +157,7 @@ export const declineConversationWithoutConfirm = async ( export async function showUpdateGroupNameByConvoId(conversationId: string) { const conversation = getConversationController().get(conversationId); - if (conversation.isMediumGroup()) { + if (conversation.isClosedGroup()) { // make sure all the members' convo exists so we can add or remove them await Promise.all( conversation @@ -170,7 +170,7 @@ export async function showUpdateGroupNameByConvoId(conversationId: string) { export async function showUpdateGroupMembersByConvoId(conversationId: string) { const conversation = getConversationController().get(conversationId); - if (conversation.isMediumGroup()) { + if (conversation.isClosedGroup()) { // make sure all the members' convo exists so we can add or remove them await Promise.all( conversation @@ -192,7 +192,7 @@ export function showLeaveGroupByConvoId(conversationId: string) { const message = window.i18n('leaveGroupConfirmation'); const ourPK = UserUtils.getOurPubKeyStrFromCache(); const isAdmin = (conversation.get('groupAdmins') || []).includes(ourPK); - const isClosedGroup = conversation.get('is_medium_group') || false; + const isClosedGroup = conversation.isClosedGroup() || false; // if this is not a closed group, or we are not admin, we can just show a confirmation dialog if (!isClosedGroup || (isClosedGroup && !isAdmin)) { diff --git a/ts/interactions/conversations/unsendingInteractions.ts b/ts/interactions/conversations/unsendingInteractions.ts index 6f42572b5..4776abe7b 100644 --- a/ts/interactions/conversations/unsendingInteractions.ts +++ b/ts/interactions/conversations/unsendingInteractions.ts @@ -126,7 +126,7 @@ export async function deleteMessagesFromSwarmAndCompletelyLocally( conversation: ConversationModel, messages: Array ) { - if (conversation.isMediumGroup()) { + if (conversation.isClosedGroup()) { window.log.info('Cannot delete message from a closed group swarm, so we just complete delete.'); await Promise.all( messages.map(async message => { @@ -162,7 +162,7 @@ export async function deleteMessagesFromSwarmAndMarkAsDeletedLocally( conversation: ConversationModel, messages: Array ) { - if (conversation.isMediumGroup()) { + if (conversation.isClosedGroup()) { window.log.info('Cannot delete messages from a closed group swarm, so we just markDeleted.'); await Promise.all( messages.map(async message => { diff --git a/ts/models/conversation.ts b/ts/models/conversation.ts index ad0d10a3c..7f5cf5061 100644 --- a/ts/models/conversation.ts +++ b/ts/models/conversation.ts @@ -111,6 +111,7 @@ import { SessionUtilUserProfile } from '../session/utils/libsession/libsession_u import { ReduxSogsRoomInfos } from '../state/ducks/sogsRoomInfo'; import { getCanWriteOutsideRedux, + getModeratorsOutsideRedux, getSubscriberCountOutsideRedux, } from '../state/selectors/sogsRoomInfo'; @@ -120,6 +121,11 @@ type InMemoryConvoInfos = { lastReadTimestampMessage: number | null; }; +// TODO decide it it makes sense to move this to a redux slice? +/** + * Some fields are not stored in the database, but are kept in memory. + * We use this map to keep track of them. The key is the conversation id. + */ const inMemoryConvoInfos: Map = new Map(); export class ConversationModel extends Backbone.Model { @@ -222,10 +228,10 @@ export class ConversationModel extends Backbone.Model { public isOpenGroupV2(): boolean { return OpenGroupUtils.isOpenGroupV2(this.id); } - public isClosedGroup() { - return ( - (this.get('type') === ConversationTypeEnum.GROUP && !this.isPublic()) || - this.get('type') === ConversationTypeEnum.GROUPV3 + public isClosedGroup(): boolean { + return Boolean( + (this.get('type') === ConversationTypeEnum.GROUP && this.id.startsWith('05')) || + (this.get('type') === ConversationTypeEnum.GROUPV3 && this.id.startsWith('03')) ); } public isPrivate() { @@ -242,17 +248,13 @@ export class ConversationModel extends Backbone.Model { return false; } - if (this.isPrivate() || this.isClosedGroup() || this.isMediumGroup()) { + if (this.isPrivate() || this.isClosedGroup()) { return BlockedNumberController.isBlocked(this.id); } return false; } - public isMediumGroup() { - return this.get('is_medium_group'); - } - /** * Returns true if this conversation is active * i.e. the conversation is visibie on the left pane. (Either we or another user created this convo). @@ -277,21 +279,8 @@ export class ConversationModel extends Backbone.Model { return groupAdmins && groupAdmins?.length > 0 ? groupAdmins : []; } - /** - * Get the list of moderators in that room, or an empty array - * Only to be called for opengroup conversations. - * This makes no sense for a private chat or an closed group, as closed group admins must be stored with getGroupAdmins - * @returns the list of moderators for the conversation if the conversation is public, or [] - */ - public getGroupModerators(): Array { - const groupModerators = this.get('groupModerators') as Array | undefined; - - return this.isPublic() && groupModerators && groupModerators?.length > 0 ? groupModerators : []; - } - // tslint:disable-next-line: cyclomatic-complexity max-func-body-length public getConversationModelProps(): ReduxConversationType { - const groupModerators = this.getGroupModerators(); const isPublic = this.isPublic(); const zombies = this.isClosedGroup() ? this.get('zombies') : []; @@ -429,10 +418,6 @@ export class ConversationModel extends Backbone.Model { if (foundCommunity.priority > 0) { toRet.isPinned = true; // TODO priority also handles sorting } - - if (groupModerators?.length) { - toRet.groupModerators = uniq(groupModerators); - } } if (foundVolatileInfo) { @@ -481,6 +466,12 @@ export class ConversationModel extends Backbone.Model { return toRet; } + /** + * + * @param groupAdmins the Array of group admins, where, if we are a group admin, we are present unblinded. + * @param shouldCommit set this to true to auto commit changes + * @returns true if the groupAdmins where not the same (and thus updated) + */ public async updateGroupAdmins(groupAdmins: Array, shouldCommit: boolean) { const sortedExistingAdmins = uniq(sortBy(this.getGroupAdmins())); const sortedNewAdmins = uniq(sortBy(groupAdmins)); @@ -495,23 +486,6 @@ export class ConversationModel extends Backbone.Model { return true; } - public async updateGroupModerators(groupModerators: Array, shouldCommit: boolean) { - if (!this.isPublic()) { - throw new Error('group moderators are only possible on SOGS'); - } - const existingModerators = uniq(sortBy(this.getGroupModerators())); - const newModerators = uniq(sortBy(groupModerators)); - - if (isEqual(existingModerators, newModerators)) { - return false; - } - this.set({ groupModerators: newModerators }); - if (shouldCommit) { - await this.commit(); - } - return true; - } - /** * Fetches from the Database an update of what are the memory only informations like mentionedUs and the unreadCount, etc */ @@ -736,7 +710,7 @@ export class ConversationModel extends Backbone.Model { return; } - if (this.isMediumGroup()) { + if (this.isClosedGroup()) { const chatMessageMediumGroup = new VisibleMessage(chatMessageParams); const closedGroupVisibleMessage = new ClosedGroupVisibleMessage({ chatMessage: chatMessageMediumGroup, @@ -751,10 +725,6 @@ export class ConversationModel extends Backbone.Model { return; } - if (this.isClosedGroup()) { - throw new Error('Legacy group are not supported anymore. You need to recreate this group.'); - } - throw new TypeError(`Invalid conversation type: '${this.get('type')}'`); } catch (e) { await message.saveErrors(e); @@ -856,7 +826,7 @@ export class ConversationModel extends Backbone.Model { return; } - if (this.isMediumGroup()) { + if (this.isClosedGroup()) { const chatMessageMediumGroup = new VisibleMessage(chatMessageParams); const closedGroupVisibleMessage = new ClosedGroupVisibleMessage({ chatMessage: chatMessageMediumGroup, @@ -876,10 +846,6 @@ export class ConversationModel extends Backbone.Model { return; } - if (this.isClosedGroup()) { - throw new Error('Legacy group are not supported anymore. You need to recreate this group.'); - } - throw new TypeError(`Invalid conversation type: '${this.get('type')}'`); } catch (e) { window.log.error(`Reaction job failed id:${reaction.id} error:`, e); @@ -1470,7 +1436,7 @@ export class ConversationModel extends Backbone.Model { return false; } - const groupModerators = this.getGroupModerators(); + const groupModerators = getModeratorsOutsideRedux(this.id as string); return Array.isArray(groupModerators) && groupModerators.includes(pubKey); } @@ -2154,11 +2120,12 @@ export class ConversationModel extends Backbone.Model { uniq(localModsOrAdmins) ); - const moderatorsOrAdminsChanged = - type === 'admins' - ? await this.updateGroupAdmins(replacedWithOurRealSessionId, false) - : await this.updateGroupModerators(replacedWithOurRealSessionId, false); - return moderatorsOrAdminsChanged; + if (type === 'admins') { + return await this.updateGroupAdmins(replacedWithOurRealSessionId, false); + } + + ReduxSogsRoomInfos.setModeratorsOutsideRedux(this.id, replacedWithOurRealSessionId); + return false; } return false; } diff --git a/ts/models/conversationAttributes.ts b/ts/models/conversationAttributes.ts index f6f5f00e8..8aaf49814 100644 --- a/ts/models/conversationAttributes.ts +++ b/ts/models/conversationAttributes.ts @@ -55,10 +55,7 @@ export interface ConversationAttributes { // 0 means inactive (undefined and null too but we try to get rid of them and only have 0 = inactive) active_at: number; - zombies: Array; // only used for closed groups. Zombies are users which left but not yet removed by the admin TODO to remove - left: boolean; lastMessageStatus: LastMessageStatusType; - /** * lastMessage is actually just a preview of the last message text, shortened to 60 chars. * This is to avoid filling the redux store with a huge last message when it's only used in the @@ -66,24 +63,26 @@ export interface ConversationAttributes { * The shortening is made in sql.ts directly. */ lastMessage: string | null; - lastJoinedTimestamp: number; // ClosedGroup: last time we were added to this group - groupModerators: Array; // for sogs only, this is the moderators in that room. - isKickedFromGroup: boolean; - is_medium_group: boolean; + avatarImageId?: number; // SOGS ONLY: avatar imageID is currently used only for sogs. It's the fileID of the image uploaded and set as the sogs avatar + + left: boolean; // GROUPS ONLY: if we left the group (communities are removed right away so it not relevant to communities) + isKickedFromGroup: boolean; // GROUPS ONLY: if we got kicked from the group (communities just stop polling and a message sent get rejected, so not relevant to communities) avatarInProfile?: string; // this is the avatar path locally once downloaded and stored in the application attachments folder - avatarImageId?: number; // avatar imageID is currently used only for sogs. It's the fileID of the image uploaded and set as the sogs avatar isTrustedForAttachmentDownload: boolean; - /** The community chat this conversation originated from (relevant to **blinded** message requests) */ - conversationIdOrigin?: string; + conversationIdOrigin?: string; // Blinded message requests ONLY: The community from which this conversation originated from + + // TODO those two items are only used for legacy closed groups and will be removed when we get rid of the legacy closed groups support + lastJoinedTimestamp: number; // ClosedGroup: last time we were added to this group // TODO to remove after legacy closed group are dropped + zombies: Array; // only used for closed groups. Zombies are users which left but not yet removed by the admin // TODO to remove after legacy closed group are dropped // =========================================================================== // All of the items below are duplicated one way or the other with libsession. // It would be nice to at some point be able to only rely on libsession dumps - // for those so there is no need to keep them in sync. + // for those so there is no need to keep them in sync, but just have them in the dumps displayNameInProfile?: string; // no matter the type of conversation, this is the real name as set by the user/name of the open or closed group nickname?: string; // this is the name WE gave to that user (only applicable to private chats, not closed group neither opengroups) @@ -92,18 +91,16 @@ export interface ConversationAttributes { avatarPointer?: string; // this is the url of the avatar on the file server v2. we use this to detect if we need to redownload the avatar from someone (not used for opengroups) expireTimer: number; - members: Array; // members are all members for this group. zombies excluded - groupAdmins: Array; // for sogs and closed group: the admins of that group. + members: Array; // groups only members are all members for this group. zombies excluded (not used for communities) + groupAdmins: Array; // for sogs and closed group: the unique admins of that group isPinned: boolean; - isApproved: boolean; - didApproveMe: boolean; - // Force the conversation as unread even if all the messages are read. Used to highlight a conversation the user wants to check again later, synced. - markedAsUnread: boolean; + isApproved: boolean; // if we sent a message request or sent a message to this contact, we approve them. If isApproved & didApproveMe, a message request becomes a contact + didApproveMe: boolean; // if our message request was approved already (or they've sent us a message request/message themselves). If isApproved & didApproveMe, a message request becomes a contact - // hides a conversation, but keep it the history and nicknames, etc. - hidden: boolean; + markedAsUnread: boolean; // Force the conversation as unread even if all the messages are read. Used to highlight a conversation the user wants to check again later, synced. + hidden: boolean; // hides a conversation, but keep it the history and nicknames, etc. Currently only supported for contacts } /** @@ -133,7 +130,6 @@ export const fillConvoAttributesWithDefaults = ( isPinned: false, isApproved: false, didApproveMe: false, - is_medium_group: false, isKickedFromGroup: false, left: false, hidden: true, diff --git a/ts/models/message.ts b/ts/models/message.ts index 902cb3c04..995773848 100644 --- a/ts/models/message.ts +++ b/ts/models/message.ts @@ -943,12 +943,12 @@ export class MessageModel extends Backbone.Model { ); } - // Here, the convo is neither an open group, a private convo or ourself. It can only be a medium group. - // For a medium group, retry send only means trigger a send again to all recipients + // Here, the convo is neither an open group, a private convo or ourself. It can only be a closed group. + // For a closed group, retry send only means trigger a send again to all recipients // as they are all polling from the same group swarm pubkey - if (!conversation.isMediumGroup()) { + if (!conversation.isClosedGroup()) { throw new Error( - 'We should only end up with a medium group here. Anything else is an error' + 'We should only end up with a closed group here. Anything else is an error' ); } diff --git a/ts/node/database_utility.ts b/ts/node/database_utility.ts index 52cdcaf4b..b63b0d69b 100644 --- a/ts/node/database_utility.ts +++ b/ts/node/database_utility.ts @@ -48,14 +48,12 @@ export function toSqliteBoolean(val: boolean): number { // this is used to make sure when storing something in the database you remember to add the wrapping for it in formatRowOfConversation const allowedKeysFormatRowOfConversation = [ 'groupAdmins', - 'groupModerators', 'members', 'zombies', 'isTrustedForAttachmentDownload', 'isPinned', 'isApproved', 'didApproveMe', - 'is_medium_group', 'mentionedUs', 'isKickedFromGroup', 'left', @@ -115,10 +113,6 @@ export function formatRowOfConversation( row.groupAdmins?.length && row.groupAdmins.length > minLengthNoParsing ? jsonToArray(row.groupAdmins) : []; - convo.groupModerators = - row.groupModerators?.length && row.groupModerators.length > minLengthNoParsing - ? jsonToArray(row.groupModerators) - : []; convo.members = row.members?.length && row.members.length > minLengthNoParsing ? jsonToArray(row.members) : []; @@ -130,7 +124,6 @@ export function formatRowOfConversation( convo.isPinned = Boolean(convo.isPinned); convo.isApproved = Boolean(convo.isApproved); convo.didApproveMe = Boolean(convo.didApproveMe); - convo.is_medium_group = Boolean(convo.is_medium_group); convo.isKickedFromGroup = Boolean(convo.isKickedFromGroup); convo.left = Boolean(convo.left); convo.markedAsUnread = Boolean(convo.markedAsUnread); @@ -173,14 +166,12 @@ export function formatRowOfConversation( const allowedKeysOfConversationAttributes = [ 'groupAdmins', - 'groupModerators', 'members', 'zombies', 'isTrustedForAttachmentDownload', 'isPinned', 'isApproved', 'didApproveMe', - 'is_medium_group', 'isKickedFromGroup', 'left', 'lastMessage', diff --git a/ts/node/migration/sessionMigrations.ts b/ts/node/migration/sessionMigrations.ts index 11ddd3769..cebd37d83 100644 --- a/ts/node/migration/sessionMigrations.ts +++ b/ts/node/migration/sessionMigrations.ts @@ -1397,6 +1397,9 @@ function updateToSessionSchemaVersion30(currentVersion: number, db: BetterSqlite ALTER TABLE ${CONVERSATIONS_TABLE} DROP COLUMN writeCapability; ALTER TABLE ${CONVERSATIONS_TABLE} DROP COLUMN uploadCapability; ALTER TABLE ${CONVERSATIONS_TABLE} DROP COLUMN subscriberCount; + ALTER TABLE ${CONVERSATIONS_TABLE} DROP COLUMN is_medium_group; + ALTER TABLE ${CONVERSATIONS_TABLE} DROP COLUMN groupModerators; + `); // mark every "active" private chats as not hidden db.prepare( diff --git a/ts/node/sql.ts b/ts/node/sql.ts index 76ffd5df3..bc6cd9b23 100644 --- a/ts/node/sql.ts +++ b/ts/node/sql.ts @@ -431,9 +431,7 @@ function saveConversation(data: ConversationAttributes): SaveConversationReturn lastMessage, lastJoinedTimestamp, groupAdmins, - groupModerators, isKickedFromGroup, - is_medium_group, avatarPointer, avatarImageId, triggerNotificationsFor, @@ -483,11 +481,7 @@ function saveConversation(data: ConversationAttributes): SaveConversationReturn lastJoinedTimestamp, groupAdmins: groupAdmins && groupAdmins.length ? arrayStrToJson(groupAdmins) : '[]', - groupModerators: - groupModerators && groupModerators.length ? arrayStrToJson(groupModerators) : '[]', isKickedFromGroup: toSqliteBoolean(isKickedFromGroup), - - is_medium_group: toSqliteBoolean(is_medium_group), avatarPointer, avatarImageId, triggerNotificationsFor, diff --git a/ts/receiver/closedGroups.ts b/ts/receiver/closedGroups.ts index 28b6698f1..8ef8b222f 100644 --- a/ts/receiver/closedGroups.ts +++ b/ts/receiver/closedGroups.ts @@ -392,7 +392,7 @@ async function handleClosedGroupEncryptionKeyPair( await removeFromCache(envelope); return; } - if (!groupConvo.isMediumGroup()) { + if (!groupConvo.isClosedGroup()) { window?.log?.warn( `Ignoring closed group encryption key pair for nonexistent medium group. ${groupPublicKey}` ); diff --git a/ts/receiver/configMessage.ts b/ts/receiver/configMessage.ts index 8fb0d1e8c..0520a1a72 100644 --- a/ts/receiver/configMessage.ts +++ b/ts/receiver/configMessage.ts @@ -477,10 +477,10 @@ async function handleConvoInfoVolatileUpdate( const foundConvo = getConversationController().get(fromWrapper.pubkeyHex); // TODO should we create the conversation if the conversation does not exist locally? Or assume that it should be coming from a contacts update? if (foundConvo) { - // this should mark all the messages sent before fromWrapper.lastRead as read and update the unreadCount console.warn( `fromWrapper from getAll1o1: ${fromWrapper.pubkeyHex}: ${fromWrapper.unread}` ); + // this should mark all the messages sent before fromWrapper.lastRead as read and update the unreadCount await foundConvo.markReadFromConfigMessage(fromWrapper.lastRead); // this commits to the DB, if needed await foundConvo.markAsUnread(fromWrapper.unread, true); diff --git a/ts/receiver/contentMessage.ts b/ts/receiver/contentMessage.ts index ba7f169cb..bcd455c9f 100644 --- a/ts/receiver/contentMessage.ts +++ b/ts/receiver/contentMessage.ts @@ -53,7 +53,7 @@ async function decryptForClosedGroup(envelope: EnvelopePlus) { window?.log?.info('received closed group message'); try { const hexEncodedGroupPublicKey = envelope.source; - if (!GroupUtils.isMediumGroup(PubKey.cast(hexEncodedGroupPublicKey))) { + if (!GroupUtils.isClosedGroup(PubKey.cast(hexEncodedGroupPublicKey))) { window?.log?.warn('received medium group message but not for an existing medium group'); throw new Error('Invalid group public key'); // invalidGroupPublicKey } diff --git a/ts/session/apis/snode_api/swarmPolling.ts b/ts/session/apis/snode_api/swarmPolling.ts index 581f62023..043cbc456 100644 --- a/ts/session/apis/snode_api/swarmPolling.ts +++ b/ts/session/apis/snode_api/swarmPolling.ts @@ -454,10 +454,7 @@ export class SwarmPolling { const closedGroupsOnly = convos.filter( (c: ConversationModel) => - (c.isMediumGroup() || PubKey.isClosedGroupV3(c.id)) && - !c.isBlocked() && - !c.get('isKickedFromGroup') && - !c.get('left') + c.isClosedGroup() && !c.isBlocked() && !c.get('isKickedFromGroup') && !c.get('left') ); closedGroupsOnly.forEach((c: any) => { diff --git a/ts/session/conversations/ConversationController.ts b/ts/session/conversations/ConversationController.ts index 9e7e2e937..70994c9e3 100644 --- a/ts/session/conversations/ConversationController.ts +++ b/ts/session/conversations/ConversationController.ts @@ -145,14 +145,6 @@ export class ConversationController { return conversation.getContactProfileNameOrShortenedPubKey(); } - public isMediumGroup(hexEncodedGroupPublicKey: string): boolean { - const convo = this.conversations.get(hexEncodedGroupPublicKey); - if (convo) { - return !!convo.isMediumGroup(); - } - return false; - } - public async getOrCreateAndWait( id: string | PubKey, type: ConversationTypeEnum diff --git a/ts/session/group/closed-group.ts b/ts/session/group/closed-group.ts index 0b0e9fa45..37cb54c65 100644 --- a/ts/session/group/closed-group.ts +++ b/ts/session/group/closed-group.ts @@ -73,10 +73,6 @@ export async function initiateClosedGroupUpdate( isGroupV3 ? ConversationTypeEnum.GROUPV3 : ConversationTypeEnum.GROUP ); - if (!convo.isMediumGroup()) { - throw new Error('Legacy group are not supported anymore.'); - } - // do not give an admins field here. We don't want to be able to update admins and // updateOrCreateClosedGroup() will update them if given the choice. const groupDetails: GroupInfo = { @@ -227,7 +223,6 @@ export async function updateOrCreateClosedGroup(details: GroupInfo) { | 'type' | 'members' | 'displayNameInProfile' - | 'is_medium_group' | 'active_at' | 'left' | 'lastJoinedTimestamp' @@ -236,7 +231,6 @@ export async function updateOrCreateClosedGroup(details: GroupInfo) { displayNameInProfile: details.name, members: details.members, type: ConversationTypeEnum.GROUP, - is_medium_group: true, active_at: details.activeAt ? details.activeAt : 0, left: details.activeAt ? false : true, lastJoinedTimestamp: details.activeAt && weWereJustAdded ? Date.now() : details.activeAt || 0, @@ -246,7 +240,7 @@ export async function updateOrCreateClosedGroup(details: GroupInfo) { conversation.set(updates); const isBlocked = details.blocked || false; - if (conversation.isClosedGroup() || conversation.isMediumGroup()) { + if (conversation.isClosedGroup()) { await BlockedNumberController.setBlocked(conversation.id as string, isBlocked); } @@ -272,7 +266,7 @@ export async function updateOrCreateClosedGroup(details: GroupInfo) { export async function leaveClosedGroup(groupId: string) { const convo = getConversationController().get(groupId); - if (!convo || !convo.isMediumGroup()) { + if (!convo || !convo.isClosedGroup()) { window?.log?.error('Cannot leave non-existing group'); return; } @@ -296,7 +290,7 @@ export async function leaveClosedGroup(groupId: string) { admins = convo.get('groupAdmins') || []; } convo.set({ members }); - convo.set({ groupAdmins: admins }); + await convo.updateGroupAdmins(admins, false); await convo.commit(); const source = UserUtils.getOurPubKeyStrFromCache(); @@ -468,7 +462,7 @@ async function generateAndSendNewEncryptionKeyPair( ); return; } - if (!groupConvo.isMediumGroup()) { + if (!groupConvo.isClosedGroup()) { window?.log?.warn( 'generateAndSendNewEncryptionKeyPair: conversation not a closed group', groupPublicKey diff --git a/ts/session/utils/Groups.ts b/ts/session/utils/Groups.ts index f6639414d..8b22c350c 100644 --- a/ts/session/utils/Groups.ts +++ b/ts/session/utils/Groups.ts @@ -13,14 +13,14 @@ export function getGroupMembers(groupId: PubKey): Array { return groupMembers.map(PubKey.cast); } -export function isMediumGroup(groupId: PubKey): boolean { +export function isClosedGroup(groupId: PubKey): boolean { const conversation = getConversationController().get(groupId.key); if (!conversation) { return false; } - return Boolean(conversation.isMediumGroup()); + return Boolean(conversation.isClosedGroup()); } export function encodeGroupPubKeyFromHex(hexGroupPublicKey: string | PubKey) { diff --git a/ts/session/utils/libsession/libsession_utils_convo_info_volatile.ts b/ts/session/utils/libsession/libsession_utils_convo_info_volatile.ts index 3676d5904..255d22070 100644 --- a/ts/session/utils/libsession/libsession_utils_convo_info_volatile.ts +++ b/ts/session/utils/libsession/libsession_utils_convo_info_volatile.ts @@ -189,7 +189,7 @@ async function refreshConvoVolatileCached( } else if (convoId.startsWith('05')) { const fromWrapper = await ConvoInfoVolatileWrapperActions.get1o1(convoId); console.warn( - `refreshMappedValues from get1o1 ${fromWrapper?.pubkeyHex} : ${fromWrapper?.unread}` + `refreshConvoVolatileCached from get1o1 ${fromWrapper?.pubkeyHex} : ${fromWrapper?.unread}` ); if (fromWrapper) { mapped1o1WrapperValues.set(convoId, fromWrapper); diff --git a/ts/session/utils/sync/syncUtils.ts b/ts/session/utils/sync/syncUtils.ts index 277c164a9..f353441a6 100644 --- a/ts/session/utils/sync/syncUtils.ts +++ b/ts/session/utils/sync/syncUtils.ts @@ -138,7 +138,7 @@ const getValidClosedGroups = async (convos: Array) => { const closedGroupModels = convos.filter( c => !!c.get('active_at') && - c.isMediumGroup() && + c.isClosedGroup() && c.get('members')?.includes(ourPubKey) && !c.get('left') && !c.get('isKickedFromGroup') && diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index d5fca234c..4c1ec3c8e 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -253,7 +253,6 @@ export interface ReduxConversationType { left?: boolean; avatarPath?: string | null; // absolute filepath to the avatar groupAdmins?: Array; // admins for closed groups and admins for open groups - groupModerators?: Array; // only for opengroups: moderators members?: Array; // members for closed groups only zombies?: Array; // members for closed groups only diff --git a/ts/state/ducks/sogsRoomInfo.tsx b/ts/state/ducks/sogsRoomInfo.tsx index 8be4d6ec6..32612ae79 100644 --- a/ts/state/ducks/sogsRoomInfo.tsx +++ b/ts/state/ducks/sogsRoomInfo.tsx @@ -1,8 +1,15 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { sortBy, uniq } from 'lodash'; +import { + getCanWriteOutsideRedux, + getCurrentSubscriberCountOutsideRedux, + getModeratorsOutsideRedux, +} from '../selectors/sogsRoomInfo'; type RoomInfo = { canWrite: boolean; subscriberCount: number; + moderators: Array; }; export type SogsRoomInfoState = { @@ -15,7 +22,7 @@ export const initialSogsRoomInfoState: SogsRoomInfoState = { function addEmptyEntryIfNeeded(state: any, convoId: string) { if (!state.rooms[convoId]) { - state.rooms[convoId] = { canWrite: true, subscriberCount: 0 }; + state.rooms[convoId] = { canWrite: true, subscriberCount: 0, moderators: [] }; } } @@ -23,6 +30,9 @@ function addEmptyEntryIfNeeded(state: any, convoId: string) { * This slice is the one holding the memory-only infos of sogs room. This includes * - writeCapability * - subscriberCount + * - moderators + * + * Note: moderators are almost never used for sogs. We mostly rely on admins, which are tracked through the conversationModel.groupAdmins attributes (and saved to DB) */ const sogsRoomInfosSlice = createSlice({ name: 'sogsRoomInfos', @@ -39,24 +49,58 @@ const sogsRoomInfosSlice = createSlice({ addEmptyEntryIfNeeded(state, action.payload.convoId); state.rooms[action.payload.convoId].canWrite = !!action.payload.canWrite; + return state; + }, + setModerators(state, action: PayloadAction<{ convoId: string; moderators: Array }>) { + addEmptyEntryIfNeeded(state, action.payload.convoId); + + state.rooms[action.payload.convoId].moderators = sortBy(uniq(action.payload.moderators)); + return state; }, }, }); const { actions, reducer } = sogsRoomInfosSlice; -const { setSubscriberCount, setCanWrite } = actions; +const { setSubscriberCount, setCanWrite, setModerators } = actions; export const ReduxSogsRoomInfos = { setSubscriberCountOutsideRedux, setCanWriteOutsideRedux, + setModeratorsOutsideRedux, sogsRoomInfoReducer: reducer, }; function setSubscriberCountOutsideRedux(convoId: string, subscriberCount: number) { + if (subscriberCount === getCurrentSubscriberCountOutsideRedux(convoId)) { + return; + } window.inboxStore?.dispatch(setSubscriberCount({ convoId, subscriberCount })); } function setCanWriteOutsideRedux(convoId: string, canWrite: boolean) { + if (getCanWriteOutsideRedux(convoId) === canWrite) { + return; + } window.inboxStore?.dispatch(setCanWrite({ convoId, canWrite })); } + +/** + * + * @param convoId the convoId of the room to set the moderators + * @param moderators the updated list of moderators + * Note: if we are a moderator that room and the room is blinded, this update needs to contain our unblinded pubkey, NOT the blinded one + */ +function setModeratorsOutsideRedux(convoId: string, moderators: Array) { + const currentMods = getModeratorsOutsideRedux(convoId); + if (sortBy(uniq(currentMods)) === sortBy(uniq(moderators))) { + return; + } + window.inboxStore?.dispatch( + setModerators({ + convoId, + moderators, + }) + ); + return undefined; +} diff --git a/ts/state/selectors/conversations.ts b/ts/state/selectors/conversations.ts index ce5d91464..29c4ae35c 100644 --- a/ts/state/selectors/conversations.ts +++ b/ts/state/selectors/conversations.ts @@ -37,7 +37,7 @@ import { ConversationTypeEnum, isOpenOrClosedGroup } from '../../models/conversa import { MessageReactsSelectorProps } from '../../components/conversation/message/message-content/MessageReactions'; import { filter, isEmpty, pick, sortBy } from 'lodash'; -import { getCanWrite, getSubscriberCount } from './sogsRoomInfo'; +import { getCanWrite, getModeratorsOutsideRedux, getSubscriberCount } from './sogsRoomInfo'; export const getConversations = (state: StateType): ConversationsStateType => state.conversations; @@ -808,14 +808,14 @@ export const isFirstUnreadMessageIdAbove = createSelector( const getMessageId = (_whatever: any, id: string) => id; +// tslint:disable: cyclomatic-complexity + export const getMessagePropsByMessageId = createSelector( - getConversations, getSortedMessagesOfSelectedConversation, getConversationLookup, getMessageId, - // tslint:disable-next-line: cyclomatic-complexity + ( - _convoState, messages: Array, conversations, id @@ -829,6 +829,7 @@ export const getMessagePropsByMessageId = createSelector( } const sender = foundMessageProps?.propsForMessage?.sender; + // foundMessageConversation is the conversation this message is const foundMessageConversation = conversations[foundMessageProps.propsForMessage.convoId]; if (!foundMessageConversation || !sender) { return undefined; @@ -846,8 +847,9 @@ export const getMessagePropsByMessageId = createSelector( const groupAdmins = (isGroup && foundMessageConversation.groupAdmins) || []; const weAreAdmin = groupAdmins.includes(ourPubkey) || false; - const groupModerators = (isGroup && foundMessageConversation.groupModerators) || []; - const weAreModerator = groupModerators.includes(ourPubkey) || false; + const weAreModerator = + (isPublic && getModeratorsOutsideRedux(foundMessageConversation.id).includes(ourPubkey)) || + false; // 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) diff --git a/ts/state/selectors/section.ts b/ts/state/selectors/section.ts index 6ef2f0b57..05154c406 100644 --- a/ts/state/selectors/section.ts +++ b/ts/state/selectors/section.ts @@ -29,3 +29,10 @@ export const getOverlayMode = createSelector( getSection, (state: SectionStateType): OverlayMode | undefined => state.overlayMode ); + +export const getIsMessageRequestOverlayShown = (state: StateType) => { + const focusedSection = getFocusedSection(state); + const overlayMode = getOverlayMode(state); + + return focusedSection === SectionType.Message && overlayMode === 'message-requests'; +}; diff --git a/ts/state/selectors/sogsRoomInfo.ts b/ts/state/selectors/sogsRoomInfo.ts index 34b61be69..9dc0527f1 100644 --- a/ts/state/selectors/sogsRoomInfo.ts +++ b/ts/state/selectors/sogsRoomInfo.ts @@ -1,4 +1,4 @@ -import { isNil } from 'lodash'; +import { isEmpty, isNil } from 'lodash'; import { SogsRoomInfoState } from '../ducks/sogsRoomInfo'; import { StateType } from '../reducer'; @@ -24,13 +24,33 @@ export function getSubscriberCount(state: StateType, selectedConvo?: string): nu return isNil(subscriberCount) ? 0 : subscriberCount; } -export function getSubscriberCountOutsideRedux(convoId: string) { +export function getModerators(state: StateType, selectedConvo?: string): Array { + if (!selectedConvo) { + return []; + } + + const moderators = getSogsRoomInfoState(state).rooms[selectedConvo]?.moderators; + + return isEmpty(moderators) ? [] : moderators; +} + +export function getSubscriberCountOutsideRedux(convoId: string): number { const state = window.inboxStore?.getState(); return state ? getSubscriberCount(state, convoId) : 0; } -export function getCanWriteOutsideRedux(convoId: string) { +export function getCanWriteOutsideRedux(convoId: string): boolean { const state = window.inboxStore?.getState(); return state ? getCanWrite(state, convoId) : false; } + +export function getModeratorsOutsideRedux(convoId: string): Array { + const state = window.inboxStore?.getState(); + return state ? getModerators(state, convoId) : []; +} + +export const getCurrentSubscriberCountOutsideRedux = (convoId?: string): number | undefined => { + const state = window.inboxStore?.getState(); + return getSubscriberCount(state, convoId); +}; diff --git a/ts/test/session/unit/models/ConversationModels_test.ts b/ts/test/session/unit/models/ConversationModels_test.ts index aa957df80..67d9e12a8 100644 --- a/ts/test/session/unit/models/ConversationModels_test.ts +++ b/ts/test/session/unit/models/ConversationModels_test.ts @@ -229,40 +229,6 @@ describe('fillConvoAttributesWithDefaults', () => { }); }); - describe('is_medium_group', () => { - it('initialize is_medium_group if not given', () => { - expect(fillConvoAttributesWithDefaults({} as ConversationAttributes)).to.have.deep.property( - 'is_medium_group', - false - ); - }); - - it('do not override is_medium_group if given', () => { - expect( - fillConvoAttributesWithDefaults({ - is_medium_group: true, - } as ConversationAttributes) - ).to.have.deep.property('is_medium_group', true); - }); - }); - - // describe('mentionedUs', () => { - // it('initialize mentionedUs if not given', () => { - // expect(fillConvoAttributesWithDefaults({} as ConversationAttributes)).to.have.deep.property( - // 'mentionedUs', - // false - // ); - // }); - - // it('do not override mentionedUs if given', () => { - // expect( - // fillConvoAttributesWithDefaults({ - // mentionedUs: true, - // } as ConversationAttributes) - // ).to.have.deep.property('mentionedUs', true); - // }); - // }); - describe('isKickedFromGroup', () => { it('initialize isKickedFromGroup if not given', () => { expect(fillConvoAttributesWithDefaults({} as ConversationAttributes)).to.have.deep.property( diff --git a/ts/test/session/unit/models/formatRowOfConversation_test.ts b/ts/test/session/unit/models/formatRowOfConversation_test.ts index 647bdf96f..29ab360f7 100644 --- a/ts/test/session/unit/models/formatRowOfConversation_test.ts +++ b/ts/test/session/unit/models/formatRowOfConversation_test.ts @@ -101,27 +101,6 @@ describe('formatRowOfConversation', () => { }); }); - describe('is_medium_group', () => { - it('initialize is_medium_group if they are not given', () => { - expect(formatRowOfConversation({}, 'test', 0, false)).to.have.deep.property( - 'is_medium_group', - false - ); - }); - - it('do not override is_medium_group if they are set in the row as integer: true', () => { - expect( - formatRowOfConversation({ is_medium_group: 1 }, 'test', 0, false) - ).to.have.deep.property('is_medium_group', true); - }); - - it('do not override is_medium_group if they are set in the row as integer: false', () => { - expect( - formatRowOfConversation({ is_medium_group: 0 }, 'test', 0, false) - ).to.have.deep.property('is_medium_group', false); - }); - }); - describe('mentionedUs', () => { it('initialize mentionedUs if they are not given', () => { expect(formatRowOfConversation({}, 'test', 0, false)).to.have.deep.property( diff --git a/ts/test/session/unit/selectors/conversations_test.ts b/ts/test/session/unit/selectors/conversations_test.ts index 6586453b5..8cb1c0712 100644 --- a/ts/test/session/unit/selectors/conversations_test.ts +++ b/ts/test/session/unit/selectors/conversations_test.ts @@ -34,7 +34,6 @@ describe('state/selectors/conversations', () => { avatarPath: '', groupAdmins: [], - groupModerators: [], lastMessage: undefined, members: [], expireTimer: 0, @@ -59,10 +58,8 @@ describe('state/selectors/conversations', () => { weAreAdmin: false, isGroup: false, isPrivate: false, - avatarPath: '', groupAdmins: [], - groupModerators: [], lastMessage: undefined, members: [], expireTimer: 0, @@ -87,10 +84,8 @@ describe('state/selectors/conversations', () => { weAreAdmin: false, isGroup: false, isPrivate: false, - avatarPath: '', groupAdmins: [], - groupModerators: [], lastMessage: undefined, members: [], expireTimer: 0, @@ -118,7 +113,6 @@ describe('state/selectors/conversations', () => { avatarPath: '', groupAdmins: [], - groupModerators: [], expireTimer: 0, lastMessage: undefined, members: [], @@ -147,7 +141,6 @@ describe('state/selectors/conversations', () => { avatarPath: '', groupAdmins: [], - groupModerators: [], lastMessage: undefined, members: [], isPinned: false, @@ -191,7 +184,6 @@ describe('state/selectors/conversations', () => { avatarPath: '', groupAdmins: [], - groupModerators: [], lastMessage: undefined, members: [], isPinned: false, @@ -220,7 +212,6 @@ describe('state/selectors/conversations', () => { avatarPath: '', groupAdmins: [], - groupModerators: [], lastMessage: undefined, members: [], isPinned: false, @@ -249,7 +240,6 @@ describe('state/selectors/conversations', () => { avatarPath: '', groupAdmins: [], - groupModerators: [], lastMessage: undefined, members: [], isPinned: true, @@ -277,7 +267,6 @@ describe('state/selectors/conversations', () => { avatarPath: '', groupAdmins: [], - groupModerators: [], lastMessage: undefined, members: [], isPinned: true, @@ -306,7 +295,6 @@ describe('state/selectors/conversations', () => { avatarPath: '', groupAdmins: [], - groupModerators: [], lastMessage: undefined, members: [], isPinned: false, diff --git a/ts/types/MessageAttachment.ts b/ts/types/MessageAttachment.ts index a997f2425..be00c445a 100644 --- a/ts/types/MessageAttachment.ts +++ b/ts/types/MessageAttachment.ts @@ -225,7 +225,7 @@ export async function deleteExternalFilesOfConversation( const { avatarInProfile } = conversationAttributes; - if (isString(avatarInProfile)) { + if (isString(avatarInProfile) && avatarInProfile.length) { await deleteOnDisk(avatarInProfile); } }