From f3cfe577643e485b509a08fe156f5159daf04c2b Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Thu, 16 Jan 2025 14:00:45 +1100 Subject: [PATCH] chore: cleanup requestresponse & communityinvitation control msg --- .../conversation/SessionMessagesList.tsx | 26 +++---- .../message/message-item/GroupInvitation.tsx | 52 +++++++++---- .../message-item/MessageRequestResponse.tsx | 25 +++++-- ts/models/conversation.ts | 1 + ts/models/message.ts | 75 ++++--------------- ts/node/sql.ts | 1 + ts/session/apis/snode_api/swarmPolling.ts | 2 +- ts/state/ducks/conversations.ts | 14 +--- ts/state/ducks/types.ts | 2 +- ts/state/selectors/conversations.ts | 17 +++-- ts/state/selectors/messages.ts | 29 +++++++ 11 files changed, 127 insertions(+), 117 deletions(-) diff --git a/ts/components/conversation/SessionMessagesList.tsx b/ts/components/conversation/SessionMessagesList.tsx index 818f6f1ad..d0b1d0a56 100644 --- a/ts/components/conversation/SessionMessagesList.tsx +++ b/ts/components/conversation/SessionMessagesList.tsx @@ -2,15 +2,8 @@ import { useLayoutEffect, useState } from 'react'; import { useSelector } from 'react-redux'; import useKey from 'react-use/lib/useKey'; -import { - PropsForDataExtractionNotification, - PropsForMessageRequestResponse, -} from '../../models/messageType'; -import { - PropsForExpirationTimer, - PropsForGroupInvitation, - PropsForGroupUpdate, -} from '../../state/ducks/conversations'; +import { PropsForDataExtractionNotification } from '../../models/messageType'; +import { PropsForExpirationTimer, PropsForGroupUpdate } from '../../state/ducks/conversations'; import { getOldBottomMessageId, getOldTopMessageId, @@ -18,7 +11,7 @@ import { } from '../../state/selectors/conversations'; import { useSelectedConversationKey } from '../../state/selectors/selectedConversation'; import { MessageDateBreak } from './message/message-item/DateBreak'; -import { GroupInvitation } from './message/message-item/GroupInvitation'; +import { CommunityInvitation } from './message/message-item/GroupInvitation'; import { GroupUpdateMessage } from './message/message-item/GroupUpdateMessage'; import { Message } from './message/message-item/Message'; import { MessageRequestResponse } from './message/message-item/MessageRequestResponse'; @@ -127,14 +120,17 @@ export const SessionMessagesList = (props: { } if (messageProps.message?.messageType === 'group-invitation') { - const msgProps = messageProps.message.props as PropsForGroupInvitation; - return [, ...componentToMerge]; + return [ + , + ...componentToMerge, + ]; } if (messageProps.message?.messageType === 'message-request-response') { - const msgProps = messageProps.message.props as PropsForMessageRequestResponse; - - return [, ...componentToMerge]; + return [ + , + ...componentToMerge, + ]; } if (messageProps.message?.messageType === 'data-extraction') { diff --git a/ts/components/conversation/message/message-item/GroupInvitation.tsx b/ts/components/conversation/message/message-item/GroupInvitation.tsx index 8265ea326..35c5ab852 100644 --- a/ts/components/conversation/message/message-item/GroupInvitation.tsx +++ b/ts/components/conversation/message/message-item/GroupInvitation.tsx @@ -2,12 +2,18 @@ import classNames from 'classnames'; import styled from 'styled-components'; +import { useMemo } from 'react'; import { acceptOpenGroupInvitation } from '../../../../interactions/messageInteractions'; -import { PropsForGroupInvitation } from '../../../../state/ducks/conversations'; import { SessionIconButton } from '../../../icon'; import { ExpirableReadableMessage } from './ExpirableReadableMessage'; - -const StyledGroupInvitation = styled.div` +import { + useMessageCommunityInvitationFullUrl, + useMessageCommunityInvitationCommunityName, + useMessageDirection, +} from '../../../../state/selectors'; +import type { WithMessageId } from '../../../../session/types/with'; + +const StyledCommunityInvitation = styled.div` background-color: var(--message-bubbles-received-background-color); &.invitation-outgoing { @@ -67,14 +73,30 @@ const StyledIconContainer = styled.div` border-radius: 100%; `; -export const GroupInvitation = (props: PropsForGroupInvitation) => { - const { messageId } = props; +export const CommunityInvitation = ({ messageId }: WithMessageId) => { + const messageDirection = useMessageDirection(messageId); const classes = ['group-invitation']; - if (props.direction === 'outgoing') { + const fullUrl = useMessageCommunityInvitationFullUrl(messageId); + const communityName = useMessageCommunityInvitationCommunityName(messageId); + + const hostname = useMemo(() => { + try { + const url = new URL(fullUrl || ''); + return url.origin; + } catch (e) { + window?.log?.warn('failed to get hostname from open groupv2 invitation', fullUrl); + return ''; + } + }, [fullUrl]); + + if (messageDirection === 'outgoing') { classes.push('invitation-outgoing'); } - const openGroupInvitation = window.i18n('communityInvitation'); + + if (!fullUrl || !hostname) { + return null; + } return ( { key={`readable-message-${messageId}`} dataTestId="control-message" > - +
{ - acceptOpenGroupInvitation(props.acceptUrl, props.serverName); + acceptOpenGroupInvitation(fullUrl, communityName); }} /> - {props.serverName} - {openGroupInvitation} - {props.url} + {communityName} + {window.i18n('communityInvitation')} + {hostname}
-
+
); }; diff --git a/ts/components/conversation/message/message-item/MessageRequestResponse.tsx b/ts/components/conversation/message/message-item/MessageRequestResponse.tsx index 95b9504da..8d7734dcc 100644 --- a/ts/components/conversation/message/message-item/MessageRequestResponse.tsx +++ b/ts/components/conversation/message/message-item/MessageRequestResponse.tsx @@ -1,17 +1,28 @@ import { useNicknameOrProfileNameOrShortenedPubkey } from '../../../../hooks/useParamSelector'; -import { PropsForMessageRequestResponse } from '../../../../models/messageType'; -import { UserUtils } from '../../../../session/utils'; +import type { WithMessageId } from '../../../../session/types/with'; +import { + useMessageAuthorIsUs, + useMessageIsUnread, + useMessageReceivedAt, +} from '../../../../state/selectors'; +import { useSelectedConversationKey } from '../../../../state/selectors/selectedConversation'; import { Flex } from '../../../basic/Flex'; import { Localizer } from '../../../basic/Localizer'; import { SpacerSM, TextWithChildren } from '../../../basic/Text'; import { ReadableMessage } from './ReadableMessage'; -// Note this should not respond to the disappearing message conversation setting so we use the ReadableMessage -export const MessageRequestResponse = (props: PropsForMessageRequestResponse) => { - const { messageId, isUnread, receivedAt, conversationId } = props; +// Note: this should not respond to the disappearing message conversation setting so we use the ReadableMessage directly +export const MessageRequestResponse = ({ messageId }: WithMessageId) => { + const conversationId = useSelectedConversationKey(); + const receivedAt = useMessageReceivedAt(messageId); + const isUnread = useMessageIsUnread(messageId) || false; + const isUs = useMessageAuthorIsUs(messageId); const name = useNicknameOrProfileNameOrShortenedPubkey(conversationId); - const isFromSync = props.source === UserUtils.getOurPubKeyStrFromCache(); + + if (!conversationId || !messageId) { + return null; + } return ( > - {isFromSync ? ( + {isUs ? ( ({ conversationId, messageId: m }))) ); diff --git a/ts/models/message.ts b/ts/models/message.ts index 9942888f9..c01f992e9 100644 --- a/ts/models/message.ts +++ b/ts/models/message.ts @@ -25,7 +25,6 @@ import { MessageGroupUpdate, MessageModelType, PropsForDataExtractionNotification, - PropsForMessageRequestResponse, fillMessageAttributesWithDefaults, } from './messageType'; @@ -87,7 +86,7 @@ import { Storage } from '../util/storage'; import { ConversationModel } from './conversation'; import { READ_MESSAGE_STATE } from './conversationAttributes'; import { ConversationInteractionStatus, ConversationInteractionType } from '../interactions/types'; -import { LastMessageStatusType } from '../state/ducks/types'; +import { LastMessageStatusType, type PropsForCallNotification } from '../state/ducks/types'; import { getGroupDisplayPictureChangeStr, getGroupNameChangeStr, @@ -132,8 +131,7 @@ export class MessageModel extends Backbone.Model { const propsForGroupInvitation = this.getPropsForGroupInvitation(); const propsForGroupUpdateMessage = this.getPropsForGroupUpdateMessage(); const propsForTimerNotification = this.getPropsForTimerNotification(); - const propsForExpiringMessage = this.getPropsForExpiringMessage(); - const propsForMessageRequestResponse = this.getPropsForMessageRequestResponse(); + const isMessageResponse = this.isMessageRequestResponse(); const propsForQuote = this.getPropsForQuote(); const callNotificationType = this.get('callNotificationType'); const interactionNotification = this.getInteractionNotification(); @@ -144,8 +142,8 @@ export class MessageModel extends Backbone.Model { if (propsForDataExtractionNotification) { messageProps.propsForDataExtractionNotification = propsForDataExtractionNotification; } - if (propsForMessageRequestResponse) { - messageProps.propsForMessageRequestResponse = propsForMessageRequestResponse; + if (isMessageResponse) { + messageProps.propsForMessageRequestResponse = isMessageResponse; } if (propsForGroupInvitation) { messageProps.propsForGroupInvitation = propsForGroupInvitation; @@ -160,17 +158,12 @@ export class MessageModel extends Backbone.Model { messageProps.propsForQuote = propsForQuote; } - if (propsForExpiringMessage) { - messageProps.propsForExpiringMessage = propsForExpiringMessage; - } - if (callNotificationType) { - messageProps.propsForCallNotification = { + const propsForCallNotification: PropsForCallNotification = { + messageId: this.id, notificationType: callNotificationType, - receivedAt: this.get('received_at') || Date.now(), - isUnread: this.isUnread(), - ...this.getPropsForExpiringMessage(), }; + messageProps.propsForCallNotification = propsForCallNotification; } if (interactionNotification) { @@ -449,7 +442,7 @@ export class MessageModel extends Backbone.Model { } } - public getPropsForExpiringMessage(): PropsForExpiringMessage { + private getPropsForExpiringMessage(): PropsForExpiringMessage { const expirationType = this.getExpirationType(); const expirationDurationMs = this.getExpireTimerSeconds() ? this.getExpireTimerSeconds() * DURATION.SECONDS @@ -477,7 +470,7 @@ export class MessageModel extends Backbone.Model { }; } - public getPropsForTimerNotification(): PropsForExpirationTimer | null { + private getPropsForTimerNotification(): PropsForExpirationTimer | null { if (!this.isExpirationTimerUpdate()) { return null; } @@ -514,31 +507,19 @@ export class MessageModel extends Backbone.Model { return basicProps; } - public getPropsForGroupInvitation(): PropsForGroupInvitation | null { + private getPropsForGroupInvitation(): PropsForGroupInvitation | null { const invitation = this.getCommunityInvitation(); if (!invitation || !invitation.url) { return null; } - let serverAddress = ''; - - try { - const url = new URL(invitation.url); - serverAddress = url.origin; - } catch (e) { - window?.log?.warn('failed to get hostname from open groupv2 invitation', invitation); - } return { serverName: invitation.name, - url: serverAddress, - acceptUrl: invitation.url, - receivedAt: this.get('received_at'), - isUnread: this.isUnread(), - ...this.getPropsForExpiringMessage(), + fullUrl: invitation.url, }; } - public getPropsForDataExtractionNotification(): PropsForDataExtractionNotification | null { + private getPropsForDataExtractionNotification(): PropsForDataExtractionNotification | null { if (!this.isDataExtractionNotification()) { return null; } @@ -560,31 +541,7 @@ export class MessageModel extends Backbone.Model { }; } - public getPropsForMessageRequestResponse(): PropsForMessageRequestResponse | null { - if (!this.isMessageRequestResponse()) { - return null; - } - const messageRequestResponse = this.get('messageRequestResponse'); - - if (!messageRequestResponse) { - window.log.warn('messageRequestResponse should not happen'); - return null; - } - - const contact = findAndFormatContact(messageRequestResponse.source); - - return { - ...messageRequestResponse, - name: contact.profileName || contact.name || messageRequestResponse.source, - messageId: this.id, - receivedAt: this.get('received_at'), - isUnread: this.isUnread(), - conversationId: this.get('conversationId'), - source: this.get('source'), - }; - } - - public getPropsForGroupUpdateMessage(): PropsForGroupUpdate | null { + private getPropsForGroupUpdateMessage(): PropsForGroupUpdate | null { const groupUpdate = this.getGroupUpdateAsArray(); if (!groupUpdate || isEmpty(groupUpdate)) { return null; @@ -782,7 +739,7 @@ export class MessageModel extends Backbone.Model { return props; } - public getPropsForPreview(): Array | null { + private getPropsForPreview(): Array | null { const previews = this.get('preview') || null; if (!previews || previews.length === 0) { @@ -807,11 +764,11 @@ export class MessageModel extends Backbone.Model { }); } - public getPropsForReacts(): ReactionList | null { + private getPropsForReacts(): ReactionList | null { return this.get('reacts') || null; } - public getPropsForQuote(): PropsForQuote | null { + private getPropsForQuote(): PropsForQuote | null { return this.get('quote') || null; } diff --git a/ts/node/sql.ts b/ts/node/sql.ts index 004887970..bfdf72500 100644 --- a/ts/node/sql.ts +++ b/ts/node/sql.ts @@ -1205,6 +1205,7 @@ function cleanUpExpirationTimerUpdateHistory( ) .all({ conversationId }); + // we keep at most one, so if we have <= 1, we can just return that nothing was removed. if (rows.length <= 1) { return []; } diff --git a/ts/session/apis/snode_api/swarmPolling.ts b/ts/session/apis/snode_api/swarmPolling.ts index 745dd7158..189b84cdd 100644 --- a/ts/session/apis/snode_api/swarmPolling.ts +++ b/ts/session/apis/snode_api/swarmPolling.ts @@ -458,7 +458,7 @@ export class SwarmPolling { resultsFromAllNamespaces ); window.log.debug( - `SwarmPolling: received confMessages:${confMessages?.length || 0}, revokedMessages:${revokedMessages?.length || 0}, , otherMessages:${otherMessages?.length || 0}, ` + `SwarmPolling: received for ${ed25519Str(pubkey)} confMessages:${confMessages?.length || 0}, revokedMessages:${revokedMessages?.length || 0}, , otherMessages:${otherMessages?.length || 0}, ` ); // We always handle the config messages first (for groups 03 or our own messages) await this.handleUserOrGroupConfMessages({ confMessages, pubkey, type }); diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index 6444ce203..f59c0e7be 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -11,11 +11,7 @@ import { ConversationAttributes, ConversationNotificationSettingType, } from '../../models/conversationAttributes'; -import { - MessageModelType, - PropsForDataExtractionNotification, - PropsForMessageRequestResponse, -} from '../../models/messageType'; +import { MessageModelType, PropsForDataExtractionNotification } from '../../models/messageType'; import { ConvoHub } from '../../session/conversations'; import { DisappearingMessages } from '../../session/disappearing_messages'; import { @@ -36,13 +32,12 @@ import { WithConvoId, WithMessageHash, WithMessageId } from '../../session/types export type MessageModelPropsWithoutConvoProps = { propsForMessage: PropsForMessageWithoutConvoProps; - propsForExpiringMessage?: PropsForExpiringMessage; propsForGroupInvitation?: PropsForGroupInvitation; propsForTimerNotification?: PropsForExpirationTimer; propsForDataExtractionNotification?: PropsForDataExtractionNotification; propsForGroupUpdateMessage?: PropsForGroupUpdate; propsForCallNotification?: PropsForCallNotification; - propsForMessageRequestResponse?: PropsForMessageRequestResponse; + propsForMessageRequestResponse?: boolean; propsForQuote?: PropsForQuote; propsForInteractionNotification?: PropsForInteractionNotification; }; @@ -137,10 +132,7 @@ export type PropsForGroupUpdate = { export type PropsForGroupInvitation = { serverName: string; - url: string; - direction: MessageModelType; - acceptUrl: string; - messageId: string; + fullUrl: string; }; export type PropsForAttachment = AttachmentType & { diff --git a/ts/state/ducks/types.ts b/ts/state/ducks/types.ts index dc1d1fbe1..8517d786a 100644 --- a/ts/state/ducks/types.ts +++ b/ts/state/ducks/types.ts @@ -6,8 +6,8 @@ import { export type CallNotificationType = 'missed-call' | 'started-call' | 'answered-a-call'; export type PropsForCallNotification = { - notificationType: CallNotificationType; messageId: string; + notificationType: CallNotificationType; }; export type LastMessageStatusType = 'sending' | 'sent' | 'read' | 'error' | undefined; diff --git a/ts/state/selectors/conversations.ts b/ts/state/selectors/conversations.ts index 9a50650d5..2f3e32c52 100644 --- a/ts/state/selectors/conversations.ts +++ b/ts/state/selectors/conversations.ts @@ -141,13 +141,14 @@ export const getSortedMessagesTypesOfSelectedConversation = createSelector( : undefined; const common = { showUnreadIndicator: isFirstUnread, showDateBreak }; + const messageIdProps = { messageId: msg.propsForMessage.id }; if (msg.propsForDataExtractionNotification) { return { ...common, message: { messageType: 'data-extraction', - props: { ...msg.propsForDataExtractionNotification, messageId: msg.propsForMessage.id }, + props: { ...msg.propsForDataExtractionNotification, ...messageIdProps }, }, }; } @@ -157,7 +158,7 @@ export const getSortedMessagesTypesOfSelectedConversation = createSelector( ...common, message: { messageType: 'message-request-response', - props: { ...msg.propsForMessageRequestResponse, messageId: msg.propsForMessage.id }, + props: messageIdProps, }, }; } @@ -167,7 +168,7 @@ export const getSortedMessagesTypesOfSelectedConversation = createSelector( ...common, message: { messageType: 'group-invitation', - props: { ...msg.propsForGroupInvitation, messageId: msg.propsForMessage.id }, + props: messageIdProps, }, }; } @@ -177,7 +178,7 @@ export const getSortedMessagesTypesOfSelectedConversation = createSelector( ...common, message: { messageType: 'group-notification', - props: { ...msg.propsForGroupUpdateMessage, messageId: msg.propsForMessage.id }, + props: { ...msg.propsForGroupUpdateMessage, ...messageIdProps }, }, }; } @@ -187,7 +188,7 @@ export const getSortedMessagesTypesOfSelectedConversation = createSelector( ...common, message: { messageType: 'timer-notification', - props: { ...msg.propsForTimerNotification, messageId: msg.propsForMessage.id }, + props: { ...msg.propsForTimerNotification, ...messageIdProps }, }, }; } @@ -199,7 +200,7 @@ export const getSortedMessagesTypesOfSelectedConversation = createSelector( messageType: 'call-notification', props: { ...msg.propsForCallNotification, - messageId: msg.propsForMessage.id, + ...messageIdProps, }, }, }; @@ -212,7 +213,7 @@ export const getSortedMessagesTypesOfSelectedConversation = createSelector( messageType: 'interaction-notification', props: { ...msg.propsForInteractionNotification, - messageId: msg.propsForMessage.id, + ...messageIdProps, }, }, }; @@ -223,7 +224,7 @@ export const getSortedMessagesTypesOfSelectedConversation = createSelector( showDateBreak, message: { messageType: 'regular-message', - props: { messageId: msg.propsForMessage.id }, + props: messageIdProps, }, }; }); diff --git a/ts/state/selectors/messages.ts b/ts/state/selectors/messages.ts index 92d1221f4..3add3e256 100644 --- a/ts/state/selectors/messages.ts +++ b/ts/state/selectors/messages.ts @@ -12,6 +12,7 @@ import { useSelectedIsPrivate } from './selectedConversation'; import { LastMessageStatusType } from '../ducks/types'; import { PubKey } from '../../session/types'; import { useIsMe } from '../../hooks/useParamSelector'; +import { UserUtils } from '../../session/utils'; function useMessagePropsByMessageId(messageId: string | undefined) { return useSelector((state: StateType) => getMessagePropsByMessageId(state, messageId)); @@ -83,6 +84,10 @@ export const useMessageAuthor = (messageId: string | undefined): string | undefi return useMessagePropsByMessageId(messageId)?.propsForMessage.sender; }; +export const useMessageAuthorIsUs = (messageId: string | undefined): boolean => { + return UserUtils.isUsFromCache(useMessagePropsByMessageId(messageId)?.propsForMessage.sender); +}; + export const useMessageDirection = ( messageId: string | undefined ): MessageModelType | undefined => { @@ -129,6 +134,10 @@ export function useMessageReceivedAt(messageId: string | undefined) { return useMessagePropsByMessageId(messageId)?.propsForMessage.receivedAt; } +export function useMessageIsUnread(messageId: string | undefined) { + return useMessagePropsByMessageId(messageId)?.propsForMessage.isUnread; +} + export function useMessageTimestamp(messageId: string | undefined) { return useMessagePropsByMessageId(messageId)?.propsForMessage.timestamp; } @@ -174,3 +183,23 @@ export function useHideAvatarInMsgList(messageId?: string, isDetailView?: boolea export function useMessageSelected(messageId?: string) { return useSelector((state: StateType) => getIsMessageSelected(state, messageId)); } + +/** + * ================================================== + * Below are selectors for community invitation props + * ================================================== + */ + +/** + * Return the full url needed to join a community through a community invitation message + */ +export function useMessageCommunityInvitationFullUrl(messageId: string) { + return useMessagePropsByMessageId(messageId)?.propsForGroupInvitation?.fullUrl; +} + +/** + * Return the community display name to have a guess of what a community is about + */ +export function useMessageCommunityInvitationCommunityName(messageId: string) { + return useMessagePropsByMessageId(messageId)?.propsForGroupInvitation?.serverName; +}