mirror of
https://github.com/oxen-io/session-desktop.git
synced 2023-12-14 02:12:57 +01:00
reactify group updates text bubble from redux store (#2083)
This commit is contained in:
parent
5fb3237d1a
commit
58dc3e26ca
|
@ -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 (
|
||||
<ReadableMessage
|
||||
messageId={messageId}
|
||||
receivedAt={receivedAt}
|
||||
isUnread={isUnread}
|
||||
key={`readable-message-${messageId}`}
|
||||
>
|
||||
<NotificationBubble notificationText={textChange} iconType="users" />
|
||||
</ReadableMessage>
|
||||
);
|
||||
};
|
|
@ -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 [<GroupNotification key={messageId} {...msgProps} />, dateBreak, unreadIndicator];
|
||||
return [<GroupUpdateMessage key={messageId} {...msgProps} />, dateBreak, unreadIndicator];
|
||||
}
|
||||
|
||||
if (messageProps.message?.messageType === 'group-invitation') {
|
||||
|
|
|
@ -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>): 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>): 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>): 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 (
|
||||
<ReadableMessage
|
||||
messageId={messageId}
|
||||
receivedAt={receivedAt}
|
||||
isUnread={isUnread}
|
||||
key={`readable-message-${messageId}`}
|
||||
>
|
||||
<NotificationBubble notificationText={ChangeItem(change)} iconType="users" />
|
||||
</ReadableMessage>
|
||||
);
|
||||
};
|
|
@ -108,7 +108,9 @@ const Seed = (props: SeedProps) => {
|
|||
<p className="session-modal__description">{i18n('recoveryPhraseSavePromptMain')}</p>
|
||||
<SpacerXS />
|
||||
|
||||
<i className="session-modal__text-highlight">{recoveryPhrase}</i>
|
||||
<i data-test-id="recovery-phrase-seed-modal" className="session-modal__text-highlight">
|
||||
{recoveryPhrase}
|
||||
</i>
|
||||
</div>
|
||||
<SpacerLG />
|
||||
<div className="session-modal__button-group">
|
||||
|
|
|
@ -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<string>) {
|
||||
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());
|
||||
}
|
||||
|
|
|
@ -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<string> | undefined) {
|
||||
return (
|
||||
arrayToCheck &&
|
||||
arrayToCheck.length === 1 &&
|
||||
(arrayToCheck[0] === UserUtils.getOurPubKeyStrFromCache() ||
|
||||
arrayToCheck[0].toLowerCase() === 'you')
|
||||
);
|
||||
}
|
||||
|
||||
export function arrayContainsOneItemOnly(arrayToCheck: Array<string> | undefined) {
|
||||
return arrayToCheck && arrayToCheck.length === 1;
|
||||
}
|
||||
|
||||
export class MessageModel extends Backbone.Model<MessageAttributes> {
|
||||
constructor(attributes: MessageAttributesOptionals & { skipTimerInit?: boolean }) {
|
||||
|
@ -86,7 +103,7 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
|
|||
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<MessageAttributes> {
|
|||
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<MessageAttributes> {
|
|||
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<MessageAttributes> {
|
|||
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<MessageAttributes> {
|
|||
}
|
||||
|
||||
// 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<MessageAttributes> {
|
|||
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<string> | undefined = Array.isArray(groupUpdate.left)
|
||||
? groupUpdate.left
|
||||
: groupUpdate.left
|
||||
? [groupUpdate.left]
|
||||
: undefined;
|
||||
const kicked: Array<string> | undefined = Array.isArray(groupUpdate.kicked)
|
||||
? groupUpdate.kicked
|
||||
: groupUpdate.kicked
|
||||
? [groupUpdate.kicked]
|
||||
: undefined;
|
||||
const joined: Array<string> | 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(() => {
|
||||
|
|
|
@ -24,7 +24,7 @@ export interface MessageAttributes {
|
|||
expires_at?: number;
|
||||
recipients: Array<string>;
|
||||
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<string>;
|
||||
joined?: Array<string>;
|
||||
kicked?: Array<string>;
|
||||
name?: string;
|
||||
};
|
||||
|
||||
export interface MessageAttributesOptionals {
|
||||
id?: string;
|
||||
source: string;
|
||||
|
@ -141,7 +148,7 @@ export interface MessageAttributesOptionals {
|
|||
expires_at?: number;
|
||||
recipients?: Array<string>;
|
||||
type: MessageModelType;
|
||||
group_update?: any;
|
||||
group_update?: MessageGroupUpdate;
|
||||
groupInvitation?: any;
|
||||
attachments?: any;
|
||||
contact?: any;
|
||||
|
|
|
@ -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<any> {
|
||||
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 });
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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<FindAndFormatContactType>;
|
||||
added: Array<string>;
|
||||
};
|
||||
|
||||
export type PropsForGroupUpdateKicked = {
|
||||
type: 'kicked';
|
||||
isMe: boolean;
|
||||
contacts?: Array<FindAndFormatContactType>;
|
||||
kicked: Array<string>;
|
||||
};
|
||||
|
||||
export type PropsForGroupUpdateRemove = {
|
||||
type: 'remove';
|
||||
isMe: boolean;
|
||||
contacts?: Array<FindAndFormatContactType>;
|
||||
export type PropsForGroupUpdateLeft = {
|
||||
type: 'left';
|
||||
left: Array<string>;
|
||||
};
|
||||
|
||||
export type PropsForGroupUpdateName = {
|
||||
|
@ -115,12 +113,10 @@ export type PropsForGroupUpdateType =
|
|||
| PropsForGroupUpdateAdd
|
||||
| PropsForGroupUpdateKicked
|
||||
| PropsForGroupUpdateName
|
||||
| PropsForGroupUpdateRemove;
|
||||
|
||||
export type PropsForGroupUpdateArray = Array<PropsForGroupUpdateType>;
|
||||
| PropsForGroupUpdateLeft;
|
||||
|
||||
export type PropsForGroupUpdate = {
|
||||
changes: PropsForGroupUpdateArray;
|
||||
change: PropsForGroupUpdateType;
|
||||
messageId: string;
|
||||
receivedAt: number | undefined;
|
||||
isUnread: boolean;
|
||||
|
|
|
@ -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 },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue