From 58dc3e26cab855796610909c832c6d178e7faf8b Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Wed, 15 Dec 2021 11:32:07 +1100 Subject: [PATCH] reactify group updates text bubble from redux store (#2083) --- .../conversation/GroupNotification.tsx | 91 ----- .../conversation/SessionMessagesList.tsx | 4 +- .../message-item/GroupUpdateMessage.tsx | 89 ++++ ts/components/dialog/SessionSeedModal.tsx | 4 +- ts/hooks/useParamSelector.ts | 15 + ts/models/message.ts | 380 +++++++++--------- ts/models/messageType.ts | 11 +- ts/receiver/queuedJob.ts | 76 ---- ts/session/group/index.ts | 2 +- ts/state/ducks/conversations.ts | 20 +- ts/state/selectors/conversations.ts | 4 +- 11 files changed, 322 insertions(+), 374 deletions(-) delete mode 100644 ts/components/conversation/GroupNotification.tsx create mode 100644 ts/components/conversation/message/message-item/GroupUpdateMessage.tsx diff --git a/ts/components/conversation/GroupNotification.tsx b/ts/components/conversation/GroupNotification.tsx deleted file mode 100644 index cba7a8f13..000000000 --- a/ts/components/conversation/GroupNotification.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import React from 'react'; - -import { - PropsForGroupUpdate, - PropsForGroupUpdateAdd, - PropsForGroupUpdateKicked, - PropsForGroupUpdateRemove, - PropsForGroupUpdateType, -} from '../../state/ducks/conversations'; -import _ from 'underscore'; -import { NotificationBubble } from './message/message-item/notification-bubble/NotificationBubble'; -import { ReadableMessage } from './message/message-item/ReadableMessage'; - -// This component is used to display group updates in the conversation view. -// This is a not a "notification" as the name suggests, but a message inside the conversation - -type TypeWithContacts = - | PropsForGroupUpdateAdd - | PropsForGroupUpdateKicked - | PropsForGroupUpdateRemove; - -function isTypeWithContact(change: PropsForGroupUpdateType): change is TypeWithContacts { - return (change as TypeWithContacts).contacts !== undefined; -} - -function getPeople(change: TypeWithContacts) { - return change.contacts?.map(c => c.profileName || c.pubkey).join(', '); -} - -// tslint:disable-next-line: cyclomatic-complexity -const ChangeItem = (change: PropsForGroupUpdateType): string => { - const people = isTypeWithContact(change) ? getPeople(change) : undefined; - - switch (change.type) { - case 'name': - return window.i18n('titleIsNow', [change.newName || '']); - case 'add': - if (!change.contacts || !change.contacts.length || !people) { - throw new Error('Group update add is missing contacts'); - } - - const joinKey = change.contacts.length > 1 ? 'multipleJoinedTheGroup' : 'joinedTheGroup'; - return window.i18n(joinKey, [people]); - case 'remove': - if (change.isMe) { - return window.i18n('youLeftTheGroup'); - } - - if (!change.contacts || !change.contacts.length || !people) { - throw new Error('Group update remove is missing contacts'); - } - - const leftKey = change.contacts.length > 1 ? 'multipleLeftTheGroup' : 'leftTheGroup'; - return window.i18n(leftKey, [people]); - - case 'kicked': - if (change.isMe) { - return window.i18n('youGotKickedFromGroup'); - } - - if (!change.contacts || !change.contacts.length || !people) { - throw new Error('Group update kicked is missing contacts'); - } - - const kickedKey = - change.contacts.length > 1 ? 'multipleKickedFromTheGroup' : 'kickedFromTheGroup'; - return window.i18n(kickedKey, [people]); - - case 'general': - return window.i18n('updatedTheGroup'); - default: - throw new Error('Missing case error'); - } -}; - -export const GroupNotification = (props: PropsForGroupUpdate) => { - const { changes, messageId, receivedAt, isUnread } = props; - - const textChange = changes.map(ChangeItem)[0]; - - return ( - - - - ); -}; diff --git a/ts/components/conversation/SessionMessagesList.tsx b/ts/components/conversation/SessionMessagesList.tsx index 2649b1585..829c8791a 100644 --- a/ts/components/conversation/SessionMessagesList.tsx +++ b/ts/components/conversation/SessionMessagesList.tsx @@ -10,7 +10,7 @@ import { PropsForGroupUpdate, } from '../../state/ducks/conversations'; import { getSortedMessagesTypesOfSelectedConversation } from '../../state/selectors/conversations'; -import { GroupNotification } from './GroupNotification'; +import { GroupUpdateMessage } from './message/message-item/GroupUpdateMessage'; import { DataExtractionNotification } from './message/message-item/DataExtractionNotification'; import { MessageDateBreak } from './message/message-item/DateBreak'; import { GroupInvitation } from './message/message-item/GroupInvitation'; @@ -63,7 +63,7 @@ export const SessionMessagesList = (props: { ) : null; if (messageProps.message?.messageType === 'group-notification') { const msgProps = messageProps.message.props as PropsForGroupUpdate; - return [, dateBreak, unreadIndicator]; + return [, dateBreak, unreadIndicator]; } if (messageProps.message?.messageType === 'group-invitation') { diff --git a/ts/components/conversation/message/message-item/GroupUpdateMessage.tsx b/ts/components/conversation/message/message-item/GroupUpdateMessage.tsx new file mode 100644 index 000000000..ce6d41d26 --- /dev/null +++ b/ts/components/conversation/message/message-item/GroupUpdateMessage.tsx @@ -0,0 +1,89 @@ +import React from 'react'; + +import { + PropsForGroupUpdate, + PropsForGroupUpdateType, +} from '../../../../state/ducks/conversations'; +import _ from 'underscore'; +import { NotificationBubble } from './notification-bubble/NotificationBubble'; +import { ReadableMessage } from './ReadableMessage'; +import { arrayContainsUsOnly } from '../../../../models/message'; +import { useConversationsUsernameOrFull } from '../../../../hooks/useParamSelector'; + +// This component is used to display group updates in the conversation view. + +const ChangeItemJoined = (added: Array): string => { + if (!added.length) { + throw new Error('Group update add is missing contacts'); + } + const names = useConversationsUsernameOrFull(added); + const joinKey = added.length > 1 ? 'multipleJoinedTheGroup' : 'joinedTheGroup'; + return window.i18n(joinKey, [names.join(', ')]); +}; + +const ChangeItemKicked = (kicked: Array): string => { + if (!kicked.length) { + throw new Error('Group update kicked is missing contacts'); + } + const names = useConversationsUsernameOrFull(kicked); + + if (arrayContainsUsOnly(kicked)) { + return window.i18n('youGotKickedFromGroup'); + } + + const kickedKey = kicked.length > 1 ? 'multipleKickedFromTheGroup' : 'kickedFromTheGroup'; + return window.i18n(kickedKey, [names.join(', ')]); +}; + +const ChangeItemLeft = (left: Array): string => { + if (!left.length) { + throw new Error('Group update remove is missing contacts'); + } + + const names = useConversationsUsernameOrFull(left); + + if (arrayContainsUsOnly(left)) { + return window.i18n('youLeftTheGroup'); + } + + const leftKey = left.length > 1 ? 'multipleLeftTheGroup' : 'leftTheGroup'; + return window.i18n(leftKey, [names.join(', ')]); +}; + +// tslint:disable-next-line: cyclomatic-complexity +const ChangeItem = (change: PropsForGroupUpdateType): string => { + switch (change.type) { + case 'name': + console.warn('name: ', change.newName); + + return window.i18n('titleIsNow', [change.newName || '']); + case 'add': + return ChangeItemJoined(change.added); + + case 'left': + return ChangeItemLeft(change.left); + + case 'kicked': + return ChangeItemKicked(change.kicked); + + case 'general': + return window.i18n('updatedTheGroup'); + default: + throw new Error('Missing case error'); + } +}; + +export const GroupUpdateMessage = (props: PropsForGroupUpdate) => { + const { change, messageId, receivedAt, isUnread } = props; + + return ( + + + + ); +}; diff --git a/ts/components/dialog/SessionSeedModal.tsx b/ts/components/dialog/SessionSeedModal.tsx index 59bf9263e..38930869b 100644 --- a/ts/components/dialog/SessionSeedModal.tsx +++ b/ts/components/dialog/SessionSeedModal.tsx @@ -108,7 +108,9 @@ const Seed = (props: SeedProps) => {

{i18n('recoveryPhraseSavePromptMain')}

- {recoveryPhrase} + + {recoveryPhrase} +
diff --git a/ts/hooks/useParamSelector.ts b/ts/hooks/useParamSelector.ts index 478cd449d..e7974c766 100644 --- a/ts/hooks/useParamSelector.ts +++ b/ts/hooks/useParamSelector.ts @@ -49,6 +49,21 @@ export function useConversationUsernameOrShorten(pubkey?: string) { }); } +/** + * Returns either the nickname, profileName, or the shorten of the pubkeys given + */ +export function useConversationsUsernameOrFull(pubkeys: Array) { + return useSelector((state: StateType) => { + return pubkeys.map(pubkey => { + if (pubkey === UserUtils.getOurPubKeyStrFromCache() || pubkey.toLowerCase() === 'you') { + return window.i18n('you'); + } + const convo = state.conversations.conversationLookup[pubkey]; + return convo?.profileName || convo?.name || pubkey; + }); + }); +} + export function useOurConversationUsername() { return useConversationUsername(UserUtils.getOurPubKeyStrFromCache()); } diff --git a/ts/models/message.ts b/ts/models/message.ts index e5aadc972..59eb997b9 100644 --- a/ts/models/message.ts +++ b/ts/models/message.ts @@ -13,6 +13,7 @@ import { fillMessageAttributesWithDefaults, MessageAttributes, MessageAttributesOptionals, + MessageGroupUpdate, MessageModelType, PropsForDataExtractionNotification, } from './messageType'; @@ -31,11 +32,10 @@ import { PropsForGroupInvitation, PropsForGroupUpdate, PropsForGroupUpdateAdd, - PropsForGroupUpdateArray, PropsForGroupUpdateGeneral, PropsForGroupUpdateKicked, + PropsForGroupUpdateLeft, PropsForGroupUpdateName, - PropsForGroupUpdateRemove, PropsForMessageWithoutConvoProps, } from '../state/ducks/conversations'; import { VisibleMessage } from '../session/messages/outgoing/visibleMessage/VisibleMessage'; @@ -51,6 +51,23 @@ import { isUsFromCache } from '../session/utils/User'; import { perfEnd, perfStart } from '../session/utils/Performance'; import { AttachmentTypeWithPath } from '../types/Attachment'; import _ from 'lodash'; +// tslint:disable: cyclomatic-complexity + +/** + * @returns true if the array contains only a single item being 'You', 'you' or our device pubkey + */ +export function arrayContainsUsOnly(arrayToCheck: Array | undefined) { + return ( + arrayToCheck && + arrayToCheck.length === 1 && + (arrayToCheck[0] === UserUtils.getOurPubKeyStrFromCache() || + arrayToCheck[0].toLowerCase() === 'you') + ); +} + +export function arrayContainsOneItemOnly(arrayToCheck: Array | undefined) { + return arrayToCheck && arrayToCheck.length === 1; +} export class MessageModel extends Backbone.Model { constructor(attributes: MessageAttributesOptionals & { skipTimerInit?: boolean }) { @@ -86,7 +103,7 @@ export class MessageModel extends Backbone.Model { perfStart(`getPropsMessage-${this.id}`); const propsForDataExtractionNotification = this.getPropsForDataExtractionNotification(); const propsForGroupInvitation = this.getPropsForGroupInvitation(); - const propsForGroupNotification = this.getPropsForGroupNotification(); + const propsForGroupUpdateMessage = this.getPropsForGroupUpdateMessage(); const propsForTimerNotification = this.getPropsForTimerNotification(); const callNotificationType = this.get('callNotificationType'); const messageProps: MessageModelPropsWithoutConvoProps = { @@ -98,8 +115,8 @@ export class MessageModel extends Backbone.Model { if (propsForGroupInvitation) { messageProps.propsForGroupInvitation = propsForGroupInvitation; } - if (propsForGroupNotification) { - messageProps.propsForGroupNotification = propsForGroupNotification; + if (propsForGroupUpdateMessage) { + messageProps.propsForGroupUpdateMessage = propsForGroupUpdateMessage; } if (propsForTimerNotification) { messageProps.propsForTimerNotification = propsForTimerNotification; @@ -132,10 +149,6 @@ export class MessageModel extends Backbone.Model { return !!(flags & expirationTimerFlag); } - public isGroupUpdate() { - return Boolean(this.get('group_update')); - } - public isIncoming() { return this.get('type') === 'incoming'; } @@ -158,118 +171,6 @@ export class MessageModel extends Backbone.Model { this.set(attributes); } - // tslint:disable-next-line: cyclomatic-complexity - public getDescription() { - if (this.isGroupUpdate()) { - const groupUpdate = this.get('group_update'); - - const ourPrimary = window.textsecure.storage.get('primaryDevicePubKey'); - if ( - groupUpdate.left === 'You' || - (Array.isArray(groupUpdate.left) && - groupUpdate.left.length === 1 && - groupUpdate.left[0] === ourPrimary) - ) { - return window.i18n('youLeftTheGroup'); - } else if ( - (groupUpdate.left && Array.isArray(groupUpdate.left) && groupUpdate.left.length === 1) || - typeof groupUpdate.left === 'string' - ) { - return window.i18n('leftTheGroup', [ - getConversationController().getContactProfileNameOrShortenedPubKey(groupUpdate.left), - ]); - } - if (groupUpdate.kicked === 'You') { - return window.i18n('youGotKickedFromGroup'); - } - - const messages = []; - if (!groupUpdate.name && !groupUpdate.joined && !groupUpdate.kicked) { - messages.push(window.i18n('updatedTheGroup')); - } - if (groupUpdate.name) { - messages.push(window.i18n('titleIsNow', groupUpdate.name)); - } - if (groupUpdate.joined && groupUpdate.joined.length) { - const names = groupUpdate.joined.map((pubKey: string) => - getConversationController().getContactProfileNameOrFullPubKey(pubKey) - ); - - if (names.length > 1) { - messages.push(window.i18n('multipleJoinedTheGroup', names.join(', '))); - } else { - messages.push(window.i18n('joinedTheGroup', names[0])); - } - } - - if (groupUpdate.kicked && groupUpdate.kicked.length) { - const names = _.map( - groupUpdate.kicked, - getConversationController().getContactProfileNameOrShortenedPubKey - ); - - if (names.length > 1) { - messages.push(window.i18n('multipleKickedFromTheGroup', [names.join(', ')])); - } else { - messages.push(window.i18n('kickedFromTheGroup', [names[0]])); - } - } - return messages.join(' '); - } - if (this.isIncoming() && this.hasErrors()) { - return window.i18n('incomingError'); - } - if (this.isGroupInvitation()) { - return `😎 ${window.i18n('openGroupInvitation')}`; - } - - if (this.isDataExtractionNotification()) { - const dataExtraction = this.get( - 'dataExtractionNotification' - ) as DataExtractionNotificationMsg; - if (dataExtraction.type === SignalService.DataExtractionNotification.Type.SCREENSHOT) { - return window.i18n('tookAScreenshot', [ - getConversationController().getContactProfileNameOrShortenedPubKey(dataExtraction.source), - ]); - } - - return window.i18n('savedTheFile', [ - getConversationController().getContactProfileNameOrShortenedPubKey(dataExtraction.source), - ]); - } - if (this.get('callNotificationType')) { - const displayName = getConversationController().getContactProfileNameOrShortenedPubKey( - this.get('conversationId') - ); - const callNotificationType = this.get('callNotificationType'); - if (callNotificationType === 'missed-call') { - return window.i18n('callMissed', [displayName]); - } - if (callNotificationType === 'started-call') { - return window.i18n('startedACall', [displayName]); - } - if (callNotificationType === 'answered-a-call') { - return window.i18n('answeredACall', [displayName]); - } - } - if (this.get('callNotificationType')) { - const displayName = getConversationController().getContactProfileNameOrShortenedPubKey( - this.get('conversationId') - ); - const callNotificationType = this.get('callNotificationType'); - if (callNotificationType === 'missed-call') { - return window.i18n('callMissed', [displayName]); - } - if (callNotificationType === 'started-call') { - return window.i18n('startedACall', [displayName]); - } - if (callNotificationType === 'answered-a-call') { - return window.i18n('answeredACall', [displayName]); - } - } - return this.get('body'); - } - public isGroupInvitation() { return !!this.get('groupInvitation'); } @@ -425,96 +326,56 @@ export class MessageModel extends Backbone.Model { } // tslint:disable-next-line: cyclomatic-complexity - public getPropsForGroupNotification(): PropsForGroupUpdate | null { - if (!this.isGroupUpdate()) { + public getPropsForGroupUpdateMessage(): PropsForGroupUpdate | null { + const groupUpdate = this.getGroupUpdateAsArray(); + + if (!groupUpdate || _.isEmpty(groupUpdate)) { return null; } - const groupUpdate = this.get('group_update'); - const changes: PropsForGroupUpdateArray = []; - if (!groupUpdate.name && !groupUpdate.left && !groupUpdate.joined && !groupUpdate.kicked) { - const change: PropsForGroupUpdateGeneral = { - type: 'general', - }; - changes.push(change); - } + const sharedProps = { + messageId: this.id, + isUnread: this.isUnread(), + receivedAt: this.get('received_at'), + }; - if (groupUpdate.joined) { + if (groupUpdate.joined?.length) { const change: PropsForGroupUpdateAdd = { type: 'add', - contacts: _.map( - Array.isArray(groupUpdate.joined) ? groupUpdate.joined : [groupUpdate.joined], - pubkey => this.findAndFormatContact(pubkey) - ), + added: groupUpdate.joined, }; - changes.push(change); + return { change, ...sharedProps }; } - if (groupUpdate.kicked === 'You') { + if (groupUpdate.kicked?.length) { const change: PropsForGroupUpdateKicked = { type: 'kicked', - isMe: true, + kicked: groupUpdate.kicked, }; - changes.push(change); - } else if (groupUpdate.kicked) { - const change: PropsForGroupUpdateKicked = { - type: 'kicked', - isMe: false, - contacts: _.map( - Array.isArray(groupUpdate.kicked) ? groupUpdate.kicked : [groupUpdate.kicked], - pubkey => this.findAndFormatContact(pubkey) - ), - }; - changes.push(change); + return { change, ...sharedProps }; } - if (groupUpdate.left === 'You') { - const change: PropsForGroupUpdateRemove = { - type: 'remove', - isMe: true, + if (groupUpdate.left?.length) { + const change: PropsForGroupUpdateLeft = { + type: 'left', + left: groupUpdate.left, }; - changes.push(change); - } else if (groupUpdate.left) { - if ( - Array.isArray(groupUpdate.left) && - groupUpdate.left.length === 1 && - groupUpdate.left[0] === UserUtils.getOurPubKeyStrFromCache() - ) { - const change: PropsForGroupUpdateRemove = { - type: 'remove', - isMe: true, - }; - changes.push(change); - } else if ( - typeof groupUpdate.left === 'string' || - (Array.isArray(groupUpdate.left) && groupUpdate.left.length === 1) - ) { - const change: PropsForGroupUpdateRemove = { - type: 'remove', - isMe: false, - contacts: _.map( - Array.isArray(groupUpdate.left) ? groupUpdate.left : [groupUpdate.left], - pubkey => this.findAndFormatContact(pubkey) - ), - }; - changes.push(change); - } + return { change, ...sharedProps }; } if (groupUpdate.name) { const change: PropsForGroupUpdateName = { type: 'name', - newName: groupUpdate.name as string, + newName: groupUpdate.name, }; - changes.push(change); + return { change, ...sharedProps }; } - return { - changes, - messageId: this.id, - isUnread: this.isUnread(), - receivedAt: this.get('received_at'), + // Just show a "Group Updated" message, not sure what was changed + const changeGeneral: PropsForGroupUpdateGeneral = { + type: 'general', }; + return { change: changeGeneral, ...sharedProps }; } public getMessagePropStatus(): LastMessageStatusType { @@ -1265,6 +1126,151 @@ export class MessageModel extends Backbone.Model { updatesToDispatch.set(this.id, this.getMessageModelProps()); trotthledAllMessagesDispatch(); } + + /** + * Before, group_update attributes could be just the string 'You' and not an array. + * Using this method to get the group update makes sure than the joined, kicked, or left are always an array of string, or undefined + */ + private getGroupUpdateAsArray() { + const groupUpdate = this.get('group_update'); + if (!groupUpdate || _.isEmpty(groupUpdate)) { + return undefined; + } + const left: Array | undefined = Array.isArray(groupUpdate.left) + ? groupUpdate.left + : groupUpdate.left + ? [groupUpdate.left] + : undefined; + const kicked: Array | undefined = Array.isArray(groupUpdate.kicked) + ? groupUpdate.kicked + : groupUpdate.kicked + ? [groupUpdate.kicked] + : undefined; + const joined: Array | undefined = Array.isArray(groupUpdate.joined) + ? groupUpdate.joined + : groupUpdate.joined + ? [groupUpdate.joined] + : undefined; + + const forcedArrayUpdate: MessageGroupUpdate = {}; + + if (left) { + forcedArrayUpdate.left = left; + } + if (joined) { + forcedArrayUpdate.joined = joined; + } + if (kicked) { + forcedArrayUpdate.kicked = kicked; + } + if (groupUpdate.name) { + forcedArrayUpdate.name = groupUpdate.name; + } + return forcedArrayUpdate; + } + + private getDescription() { + const groupUpdate = this.getGroupUpdateAsArray(); + if (groupUpdate) { + if (arrayContainsUsOnly(groupUpdate.kicked)) { + return window.i18n('youGotKickedFromGroup'); + } + if (arrayContainsUsOnly(groupUpdate.left)) { + return window.i18n('youLeftTheGroup'); + } + + if (groupUpdate.left && groupUpdate.left.length === 1) { + return window.i18n('leftTheGroup', [ + getConversationController().getContactProfileNameOrShortenedPubKey(groupUpdate.left[0]), + ]); + } + + const messages = []; + if (!groupUpdate.name && !groupUpdate.joined && !groupUpdate.kicked && !groupUpdate.kicked) { + return window.i18n('updatedTheGroup'); // Group Updated + } + if (groupUpdate.name) { + return window.i18n('titleIsNow', [groupUpdate.name]); + } + if (groupUpdate.joined && groupUpdate.joined.length) { + const names = groupUpdate.joined.map((pubKey: string) => + getConversationController().getContactProfileNameOrFullPubKey(pubKey) + ); + + if (names.length > 1) { + messages.push(window.i18n('multipleJoinedTheGroup', [names.join(', ')])); + } else { + messages.push(window.i18n('joinedTheGroup', names)); + } + } + + if (groupUpdate.kicked && groupUpdate.kicked.length) { + const names = _.map( + groupUpdate.kicked, + getConversationController().getContactProfileNameOrShortenedPubKey + ); + + if (names.length > 1) { + messages.push(window.i18n('multipleKickedFromTheGroup', [names.join(', ')])); + } else { + messages.push(window.i18n('kickedFromTheGroup', names)); + } + } + return messages.join(' '); + } + if (this.isIncoming() && this.hasErrors()) { + return window.i18n('incomingError'); + } + if (this.isGroupInvitation()) { + return `😎 ${window.i18n('openGroupInvitation')}`; + } + + if (this.isDataExtractionNotification()) { + const dataExtraction = this.get( + 'dataExtractionNotification' + ) as DataExtractionNotificationMsg; + if (dataExtraction.type === SignalService.DataExtractionNotification.Type.SCREENSHOT) { + return window.i18n('tookAScreenshot', [ + getConversationController().getContactProfileNameOrShortenedPubKey(dataExtraction.source), + ]); + } + + return window.i18n('savedTheFile', [ + getConversationController().getContactProfileNameOrShortenedPubKey(dataExtraction.source), + ]); + } + if (this.get('callNotificationType')) { + const displayName = getConversationController().getContactProfileNameOrShortenedPubKey( + this.get('conversationId') + ); + const callNotificationType = this.get('callNotificationType'); + if (callNotificationType === 'missed-call') { + return window.i18n('callMissed', [displayName]); + } + if (callNotificationType === 'started-call') { + return window.i18n('startedACall', [displayName]); + } + if (callNotificationType === 'answered-a-call') { + return window.i18n('answeredACall', [displayName]); + } + } + if (this.get('callNotificationType')) { + const displayName = getConversationController().getContactProfileNameOrShortenedPubKey( + this.get('conversationId') + ); + const callNotificationType = this.get('callNotificationType'); + if (callNotificationType === 'missed-call') { + return window.i18n('callMissed', [displayName]); + } + if (callNotificationType === 'started-call') { + return window.i18n('startedACall', [displayName]); + } + if (callNotificationType === 'answered-a-call') { + return window.i18n('answeredACall', [displayName]); + } + } + return this.get('body'); + } } const trotthledAllMessagesDispatch = _.throttle(() => { diff --git a/ts/models/messageType.ts b/ts/models/messageType.ts index 0730f51ea..b2455de9b 100644 --- a/ts/models/messageType.ts +++ b/ts/models/messageType.ts @@ -24,7 +24,7 @@ export interface MessageAttributes { expires_at?: number; recipients: Array; type: MessageModelType; - group_update?: any; + group_update?: MessageGroupUpdate; groupInvitation?: any; attachments?: any; conversationId: string; @@ -125,6 +125,13 @@ export type PropsForDataExtractionNotification = DataExtractionNotificationMsg & isUnread: boolean; }; +export type MessageGroupUpdate = { + left?: Array; + joined?: Array; + kicked?: Array; + name?: string; +}; + export interface MessageAttributesOptionals { id?: string; source: string; @@ -141,7 +148,7 @@ export interface MessageAttributesOptionals { expires_at?: number; recipients?: Array; type: MessageModelType; - group_update?: any; + group_update?: MessageGroupUpdate; groupInvitation?: any; attachments?: any; contact?: any; diff --git a/ts/receiver/queuedJob.ts b/ts/receiver/queuedJob.ts index 274770287..f3915ddb2 100644 --- a/ts/receiver/queuedJob.ts +++ b/ts/receiver/queuedJob.ts @@ -3,8 +3,6 @@ import { queueAttachmentDownloads } from './attachments'; import { Quote } from './types'; import { PubKey } from '../session/types'; import _ from 'lodash'; -import { SignalService } from '../protobuf'; -import { UserUtils } from '../session/utils'; import { getConversationController } from '../session/conversations'; import { ConversationModel, ConversationTypeEnum } from '../models/conversation'; import { MessageModel } from '../models/message'; @@ -13,71 +11,6 @@ import { MessageModelPropsWithoutConvoProps, messagesAdded } from '../state/duck import { updateProfileOneAtATime } from './dataMessage'; import Long from 'long'; -async function handleGroups( - conversation: ConversationModel, - group: any, - source: any -): Promise { - const GROUP_TYPES = SignalService.GroupContext.Type; - - let groupUpdate = null; - - // conversation attributes - const attributes: any = { - type: 'group', - groupId: group.id, - ...conversation.attributes, - }; - - const oldMembers = conversation.get('members'); - - if (group.type === GROUP_TYPES.UPDATE) { - attributes.name = group.name; - attributes.members = group.members; - - groupUpdate = conversation.changedAttributes(_.pick(group, 'name', 'avatar')) || {}; - - const addedMembers = _.difference(attributes.members, oldMembers); - if (addedMembers.length > 0) { - groupUpdate.joined = addedMembers; - } - if (conversation.get('left')) { - // TODO: Maybe we shouldn't assume this message adds us: - // we could maybe still get this message by mistake - window?.log?.warn('re-added to a left group'); - attributes.left = false; - } - - if (attributes.isKickedFromGroup) { - // Assume somebody re-invited us since we received this update - attributes.isKickedFromGroup = false; - } - - // Check if anyone got kicked: - const removedMembers = _.difference(oldMembers, attributes.members); - const ourDeviceWasRemoved = removedMembers.some(member => UserUtils.isUsFromCache(member)); - - if (ourDeviceWasRemoved) { - groupUpdate.kicked = 'You'; - attributes.isKickedFromGroup = true; - } else if (removedMembers.length) { - groupUpdate.kicked = removedMembers; - } - } else if (group.type === GROUP_TYPES.QUIT) { - if (UserUtils.isUsFromCache(source)) { - attributes.left = true; - groupUpdate = { left: 'You' }; - } else { - groupUpdate = { left: source }; - } - attributes.members = _.without(oldMembers, source); - } - - conversation.set(attributes); - - return groupUpdate; -} - function contentTypeSupported(type: string): boolean { const Chrome = window.Signal.Util.GoogleChrome; return Chrome.isImageTypeSupported(type) || Chrome.isVideoTypeSupported(type); @@ -271,15 +204,6 @@ async function handleRegularMessage( const now = Date.now(); - // Medium groups might have `group` set even if with group chat messages... - if (dataMessage.group && !conversation.isMediumGroup()) { - // This is not necessarily a group update message, it could also be a regular group message - const groupUpdate = await handleGroups(conversation, dataMessage.group, source); - if (groupUpdate !== null) { - message.set({ group_update: groupUpdate }); - } - } - if (dataMessage.openGroupInvitation) { message.set({ groupInvitation: dataMessage.openGroupInvitation }); } diff --git a/ts/session/group/index.ts b/ts/session/group/index.ts index 70e36f876..094c74b77 100644 --- a/ts/session/group/index.ts +++ b/ts/session/group/index.ts @@ -328,7 +328,7 @@ export async function leaveClosedGroup(groupId: string) { const diffTimestamp = Date.now() - getLatestTimestampOffset(); const dbMessage = await convo.addSingleMessage({ - group_update: { left: 'You' }, + group_update: { left: [source] }, conversationId: groupId, source, type: 'outgoing', diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index 23da3c4a1..d19a83231 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -30,7 +30,7 @@ export type MessageModelPropsWithoutConvoProps = { propsForGroupInvitation?: PropsForGroupInvitation; propsForTimerNotification?: PropsForExpirationTimer; propsForDataExtractionNotification?: PropsForDataExtractionNotification; - propsForGroupNotification?: PropsForGroupUpdate; + propsForGroupUpdateMessage?: PropsForGroupUpdate; propsForCallNotification?: PropsForCallNotification; }; @@ -90,19 +90,17 @@ export type PropsForGroupUpdateGeneral = { export type PropsForGroupUpdateAdd = { type: 'add'; - contacts?: Array; + added: Array; }; export type PropsForGroupUpdateKicked = { type: 'kicked'; - isMe: boolean; - contacts?: Array; + kicked: Array; }; -export type PropsForGroupUpdateRemove = { - type: 'remove'; - isMe: boolean; - contacts?: Array; +export type PropsForGroupUpdateLeft = { + type: 'left'; + left: Array; }; export type PropsForGroupUpdateName = { @@ -115,12 +113,10 @@ export type PropsForGroupUpdateType = | PropsForGroupUpdateAdd | PropsForGroupUpdateKicked | PropsForGroupUpdateName - | PropsForGroupUpdateRemove; - -export type PropsForGroupUpdateArray = Array; + | PropsForGroupUpdateLeft; export type PropsForGroupUpdate = { - changes: PropsForGroupUpdateArray; + change: PropsForGroupUpdateType; messageId: string; receivedAt: number | undefined; isUnread: boolean; diff --git a/ts/state/selectors/conversations.ts b/ts/state/selectors/conversations.ts index 936afa682..781d9cfa1 100644 --- a/ts/state/selectors/conversations.ts +++ b/ts/state/selectors/conversations.ts @@ -222,13 +222,13 @@ export const getSortedMessagesTypesOfSelectedConversation = createSelector( }; } - if (msg.propsForGroupNotification) { + if (msg.propsForGroupUpdateMessage) { return { showUnreadIndicator: isFirstUnread, showDateBreak, message: { messageType: 'group-notification', - props: { ...msg.propsForGroupNotification, messageId: msg.propsForMessage.id }, + props: { ...msg.propsForGroupUpdateMessage, messageId: msg.propsForMessage.id }, }, }; }