Remove GroupContext proto
Co-authored-by: Scott Nonnenberg <scott@signal.org>
This commit is contained in:
parent
9bfbee464b
commit
68ae25f5cd
16 changed files with 74 additions and 713 deletions
|
@ -354,7 +354,7 @@ message DataMessage {
|
|||
|
||||
optional string body = 1;
|
||||
repeated AttachmentPointer attachments = 2;
|
||||
optional GroupContext group = 3;
|
||||
reserved /*groupV1*/ 3;
|
||||
optional GroupContextV2 groupV2 = 15;
|
||||
optional uint32 flags = 4;
|
||||
optional uint32 expireTimer = 5;
|
||||
|
@ -664,23 +664,6 @@ message AttachmentPointer {
|
|||
// Next ID: 16
|
||||
}
|
||||
|
||||
message GroupContext {
|
||||
enum Type {
|
||||
UNKNOWN = 0;
|
||||
UPDATE = 1;
|
||||
DELIVER = 2;
|
||||
QUIT = 3;
|
||||
REQUEST_INFO = 4;
|
||||
}
|
||||
|
||||
optional bytes id = 1;
|
||||
optional Type type = 2;
|
||||
optional string name = 3;
|
||||
repeated string membersE164 = 4;
|
||||
// field 6 was removed; do not use
|
||||
optional AttachmentPointer avatar = 5;
|
||||
}
|
||||
|
||||
message GroupContextV2 {
|
||||
optional bytes masterKey = 1;
|
||||
optional uint32 revision = 2;
|
||||
|
|
|
@ -2702,7 +2702,7 @@ export async function startApp(): Promise<void> {
|
|||
// Note: this type of message is automatically removed from cache in MessageReceiver
|
||||
|
||||
const { typing, sender, senderUuid, senderDevice } = ev;
|
||||
const { groupId, groupV2Id, started } = typing || {};
|
||||
const { groupV2Id, started } = typing || {};
|
||||
|
||||
// We don't do anything with incoming typing messages if the setting is disabled
|
||||
if (!window.storage.get('typingIndicators')) {
|
||||
|
@ -2721,11 +2721,7 @@ export async function startApp(): Promise<void> {
|
|||
// We multiplex between GV1/GV2 groups here, but we don't kick off migrations
|
||||
if (groupV2Id) {
|
||||
conversation = window.ConversationController.get(groupV2Id);
|
||||
}
|
||||
if (!conversation && groupId) {
|
||||
conversation = window.ConversationController.get(groupId);
|
||||
}
|
||||
if (!groupV2Id && !groupId) {
|
||||
} else {
|
||||
conversation = senderConversation;
|
||||
}
|
||||
|
||||
|
@ -2737,7 +2733,7 @@ export async function startApp(): Promise<void> {
|
|||
}
|
||||
if (!conversation) {
|
||||
log.warn(
|
||||
`onTyping: Did not find conversation for typing indicator (groupv2(${groupV2Id}), group(${groupId}), ${sender}, ${senderUuid})`
|
||||
`onTyping: Did not find conversation for typing indicator (groupv2(${groupV2Id}), ${sender}, ${senderUuid})`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
@ -2988,8 +2984,6 @@ export async function startApp(): Promise<void> {
|
|||
|
||||
const messageDescriptor = getMessageDescriptor({
|
||||
message: data.message,
|
||||
source: data.source,
|
||||
sourceUuid: data.sourceUuid,
|
||||
// 'message' event: for 1:1 converations, the conversation is same as sender
|
||||
destination: data.source,
|
||||
destinationUuid: data.sourceUuid,
|
||||
|
@ -3290,18 +3284,13 @@ export async function startApp(): Promise<void> {
|
|||
return new window.Whisper.Message(partialMessage);
|
||||
}
|
||||
|
||||
// Works with 'sent' and 'message' data sent from MessageReceiver, with a little massage
|
||||
// at callsites to make sure both source and destination are populated.
|
||||
// Works with 'sent' and 'message' data sent from MessageReceiver
|
||||
const getMessageDescriptor = ({
|
||||
message,
|
||||
source,
|
||||
sourceUuid,
|
||||
destination,
|
||||
destinationUuid,
|
||||
}: {
|
||||
message: ProcessedDataMessage;
|
||||
source?: string;
|
||||
sourceUuid?: string;
|
||||
destination?: string;
|
||||
destinationUuid?: string;
|
||||
}): MessageDescriptor => {
|
||||
|
@ -3342,44 +3331,6 @@ export async function startApp(): Promise<void> {
|
|||
id: conversationId,
|
||||
};
|
||||
}
|
||||
if (message.group) {
|
||||
const { id, derivedGroupV2Id } = message.group;
|
||||
if (!id) {
|
||||
throw new Error('getMessageDescriptor: GroupV1 data was missing id');
|
||||
}
|
||||
if (!derivedGroupV2Id) {
|
||||
log.warn(
|
||||
'getMessageDescriptor: GroupV1 data was missing derivedGroupV2Id'
|
||||
);
|
||||
} else {
|
||||
// First we check for an already-migrated GroupV2 group
|
||||
const migratedGroup =
|
||||
window.ConversationController.get(derivedGroupV2Id);
|
||||
if (migratedGroup) {
|
||||
return {
|
||||
type: Message.GROUP,
|
||||
id: migratedGroup.id,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// If we can't find one, we treat this as a normal GroupV1 group
|
||||
const { conversation: fromContact } =
|
||||
window.ConversationController.maybeMergeContacts({
|
||||
aci: sourceUuid,
|
||||
e164: source,
|
||||
reason: `getMessageDescriptor(${message.timestamp}): group v1`,
|
||||
});
|
||||
|
||||
const conversationId = window.ConversationController.ensureGroup(id, {
|
||||
addedBy: fromContact.id,
|
||||
});
|
||||
|
||||
return {
|
||||
type: Message.GROUP,
|
||||
id: conversationId,
|
||||
};
|
||||
}
|
||||
|
||||
const conversation = window.ConversationController.lookupOrCreate({
|
||||
uuid: destinationUuid,
|
||||
|
@ -3406,10 +3357,6 @@ export async function startApp(): Promise<void> {
|
|||
|
||||
const messageDescriptor = getMessageDescriptor({
|
||||
...data,
|
||||
|
||||
// 'sent' event: the sender is always us!
|
||||
source,
|
||||
sourceUuid,
|
||||
});
|
||||
|
||||
const { PROFILE_KEY_UPDATE } = Proto.DataMessage.Flags;
|
||||
|
@ -3765,18 +3712,12 @@ export async function startApp(): Promise<void> {
|
|||
function onMessageRequestResponse(ev: MessageRequestResponseEvent): void {
|
||||
ev.confirm();
|
||||
|
||||
const {
|
||||
threadE164,
|
||||
threadUuid,
|
||||
groupId,
|
||||
groupV2Id,
|
||||
messageRequestResponseType,
|
||||
} = ev;
|
||||
const { threadE164, threadUuid, groupV2Id, messageRequestResponseType } =
|
||||
ev;
|
||||
|
||||
log.info('onMessageRequestResponse', {
|
||||
threadE164,
|
||||
threadUuid,
|
||||
groupId: `group(${groupId})`,
|
||||
groupV2Id: `groupv2(${groupV2Id})`,
|
||||
messageRequestResponseType,
|
||||
});
|
||||
|
@ -3789,7 +3730,6 @@ export async function startApp(): Promise<void> {
|
|||
const attributes: MessageRequestAttributesType = {
|
||||
threadE164,
|
||||
threadUuid,
|
||||
groupId,
|
||||
groupV2Id,
|
||||
type: messageRequestResponseType,
|
||||
};
|
||||
|
|
|
@ -404,6 +404,44 @@ export class ConversationHeader extends React.Component<PropsType, StateType> {
|
|||
|
||||
const hasGV2AdminEnabled = isGroup && groupVersion === 2;
|
||||
|
||||
if (isGroup && groupVersion !== 2) {
|
||||
return (
|
||||
<ContextMenu id={triggerId}>
|
||||
<MenuItem
|
||||
onClick={() =>
|
||||
pushPanelForConversation({ type: PanelType.GroupV1Members })
|
||||
}
|
||||
>
|
||||
{i18n('icu:showMembers')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={() =>
|
||||
pushPanelForConversation({ type: PanelType.AllMedia })
|
||||
}
|
||||
>
|
||||
{i18n('icu:viewRecentMedia')}
|
||||
</MenuItem>
|
||||
<MenuItem divider />
|
||||
{isArchived ? (
|
||||
<MenuItem onClick={() => onMoveToInbox(id)}>
|
||||
{i18n('icu:moveConversationToInbox')}
|
||||
</MenuItem>
|
||||
) : (
|
||||
<MenuItem onClick={() => onArchive(id)}>
|
||||
{i18n('icu:archiveConversation')}
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem
|
||||
onClick={() =>
|
||||
this.setState({ hasDeleteMessagesConfirmation: true })
|
||||
}
|
||||
>
|
||||
{i18n('icu:deleteMessages')}
|
||||
</MenuItem>
|
||||
</ContextMenu>
|
||||
);
|
||||
}
|
||||
|
||||
const isActiveExpireTimer = (value: number): boolean => {
|
||||
if (!expireTimer) {
|
||||
return value === 0;
|
||||
|
@ -487,15 +525,6 @@ export class ConversationHeader extends React.Component<PropsType, StateType> {
|
|||
: i18n('icu:showConversationDetails--direct')}
|
||||
</MenuItem>
|
||||
) : null}
|
||||
{isGroup && !hasGV2AdminEnabled ? (
|
||||
<MenuItem
|
||||
onClick={() =>
|
||||
pushPanelForConversation({ type: PanelType.GroupV1Members })
|
||||
}
|
||||
>
|
||||
{i18n('icu:showMembers')}
|
||||
</MenuItem>
|
||||
) : null}
|
||||
<MenuItem
|
||||
onClick={() => pushPanelForConversation({ type: PanelType.AllMedia })}
|
||||
>
|
||||
|
|
|
@ -230,7 +230,6 @@ export async function sendDeleteForEveryone(
|
|||
abortSignal,
|
||||
contentHint,
|
||||
groupSendOptions: {
|
||||
groupV1: conversation.getGroupV1Info(recipients),
|
||||
groupV2: groupV2Info,
|
||||
deletedForEveryoneTimestamp: targetTimestamp,
|
||||
timestamp,
|
||||
|
|
|
@ -257,9 +257,6 @@ export async function sendNormalMessage(
|
|||
contact,
|
||||
deletedForEveryoneTimestamp,
|
||||
expireTimer,
|
||||
groupV1: conversation.getGroupV1Info(
|
||||
recipientIdentifiersWithoutMe
|
||||
),
|
||||
groupV2: groupV2Info,
|
||||
messageText: body,
|
||||
preview,
|
||||
|
|
|
@ -152,7 +152,6 @@ export async function sendProfileKey(
|
|||
contentHint,
|
||||
groupSendOptions: {
|
||||
flags: Proto.DataMessage.Flags.PROFILE_KEY_UPDATE,
|
||||
groupV1: conversation.getGroupV1Info(),
|
||||
groupV2: groupV2Info,
|
||||
profileKey,
|
||||
timestamp,
|
||||
|
|
|
@ -255,9 +255,6 @@ export async function sendReaction(
|
|||
abortSignal,
|
||||
contentHint: ContentHint.RESENDABLE,
|
||||
groupSendOptions: {
|
||||
groupV1: conversation.getGroupV1Info(
|
||||
recipientIdentifiersWithoutMe
|
||||
),
|
||||
groupV2: groupV2Info,
|
||||
reaction: reactionForSend,
|
||||
timestamp: pendingReaction.timestamp,
|
||||
|
|
|
@ -11,7 +11,6 @@ import * as Errors from '../types/errors';
|
|||
export type MessageRequestAttributesType = {
|
||||
threadE164?: string;
|
||||
threadUuid?: string;
|
||||
groupId?: string;
|
||||
groupV2Id?: string;
|
||||
type: number;
|
||||
};
|
||||
|
@ -56,20 +55,6 @@ export class MessageRequests extends Collection<MessageRequestModel> {
|
|||
}
|
||||
}
|
||||
|
||||
// V1 Group
|
||||
if (conversation.get('groupId')) {
|
||||
const syncByGroupId = this.findWhere({
|
||||
groupId: conversation.get('groupId'),
|
||||
});
|
||||
if (syncByGroupId) {
|
||||
log.info(
|
||||
`Found early message request response for group v1 ID ${conversation.idForLogging()}`
|
||||
);
|
||||
this.remove(syncByGroupId);
|
||||
return syncByGroupId;
|
||||
}
|
||||
}
|
||||
|
||||
// V2 group
|
||||
if (conversation.get('groupId')) {
|
||||
const syncByGroupId = this.findWhere({
|
||||
|
@ -91,7 +76,6 @@ export class MessageRequests extends Collection<MessageRequestModel> {
|
|||
try {
|
||||
const threadE164 = sync.get('threadE164');
|
||||
const threadUuid = sync.get('threadUuid');
|
||||
const groupId = sync.get('groupId');
|
||||
const groupV2Id = sync.get('groupV2Id');
|
||||
|
||||
let conversation;
|
||||
|
@ -100,9 +84,6 @@ export class MessageRequests extends Collection<MessageRequestModel> {
|
|||
if (groupV2Id) {
|
||||
conversation = window.ConversationController.get(groupV2Id);
|
||||
}
|
||||
if (!conversation && groupId) {
|
||||
conversation = window.ConversationController.get(groupId);
|
||||
}
|
||||
if (!conversation && (threadE164 || threadUuid)) {
|
||||
conversation = window.ConversationController.lookupOrCreate({
|
||||
e164: threadE164,
|
||||
|
@ -113,7 +94,7 @@ export class MessageRequests extends Collection<MessageRequestModel> {
|
|||
|
||||
if (!conversation) {
|
||||
log.warn(
|
||||
`Received message request response for unknown conversation: groupv2(${groupV2Id}) group(${groupId}) ${threadUuid} ${threadE164}`
|
||||
`Received message request response for unknown conversation: groupv2(${groupV2Id}) ${threadUuid} ${threadE164}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -40,7 +40,6 @@ import * as Stickers from '../types/Stickers';
|
|||
import { StorySendMode } from '../types/Stories';
|
||||
import type {
|
||||
ContactWithHydratedAvatar,
|
||||
GroupV1InfoType,
|
||||
GroupV2InfoType,
|
||||
} from '../textsecure/SendMessage';
|
||||
import createTaskWithTimeout from '../textsecure/TaskWithTimeout';
|
||||
|
@ -1307,24 +1306,6 @@ export class ConversationModel extends window.Backbone
|
|||
};
|
||||
}
|
||||
|
||||
getGroupV1Info(members?: Array<string>): GroupV1InfoType | undefined {
|
||||
const groupId = this.get('groupId');
|
||||
const groupVersion = this.get('groupVersion');
|
||||
|
||||
if (
|
||||
isDirectConversation(this.attributes) ||
|
||||
!groupId ||
|
||||
(groupVersion && groupVersion > 0)
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
id: groupId,
|
||||
members: members || this.getRecipients(),
|
||||
};
|
||||
}
|
||||
|
||||
getGroupIdBuffer(): Uint8Array | undefined {
|
||||
const groupIdString = this.get('groupId');
|
||||
|
||||
|
@ -2457,9 +2438,7 @@ export class ConversationModel extends window.Backbone
|
|||
this.disableProfileSharing({ viaStorageServiceSync });
|
||||
|
||||
if (isLocalAction) {
|
||||
if (isGroupV1(this.attributes)) {
|
||||
await this.leaveGroup();
|
||||
} else if (isGroupV2(this.attributes)) {
|
||||
if (isGroupV2(this.attributes)) {
|
||||
await this.leaveGroupV2();
|
||||
}
|
||||
}
|
||||
|
@ -2477,9 +2456,7 @@ export class ConversationModel extends window.Backbone
|
|||
'deleted from message request'
|
||||
);
|
||||
|
||||
if (isGroupV1(this.attributes)) {
|
||||
await this.leaveGroup();
|
||||
} else if (isGroupV2(this.attributes)) {
|
||||
if (isGroupV2(this.attributes)) {
|
||||
await this.leaveGroupV2();
|
||||
}
|
||||
}
|
||||
|
@ -2499,9 +2476,7 @@ export class ConversationModel extends window.Backbone
|
|||
'blocked and deleted from message request'
|
||||
);
|
||||
|
||||
if (isGroupV1(this.attributes)) {
|
||||
await this.leaveGroup();
|
||||
} else if (isGroupV2(this.attributes)) {
|
||||
if (isGroupV2(this.attributes)) {
|
||||
await this.leaveGroupV2();
|
||||
}
|
||||
}
|
||||
|
@ -4875,59 +4850,6 @@ export class ConversationModel extends window.Backbone
|
|||
return !this.get('left');
|
||||
}
|
||||
|
||||
// Deprecated: only applies to GroupV1
|
||||
async leaveGroup(): Promise<void> {
|
||||
const { messaging } = window.textsecure;
|
||||
if (!messaging) {
|
||||
throw new Error('leaveGroup: Cannot leave v1 group when offline!');
|
||||
}
|
||||
|
||||
if (!isGroupV1(this.attributes)) {
|
||||
throw new Error(
|
||||
`leaveGroup: Group ${this.idForLogging()} is not GroupV1!`
|
||||
);
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
const groupId = this.get('groupId');
|
||||
|
||||
if (!groupId) {
|
||||
throw new Error(`leaveGroup/${this.idForLogging()}: No groupId!`);
|
||||
}
|
||||
|
||||
const groupIdentifiers = this.getRecipients();
|
||||
this.set({ left: true });
|
||||
window.Signal.Data.updateConversation(this.attributes);
|
||||
|
||||
const model = new window.Whisper.Message({
|
||||
conversationId: this.id,
|
||||
group_update: { left: 'You' },
|
||||
readStatus: ReadStatus.Read,
|
||||
received_at_ms: now,
|
||||
received_at: incrementMessageCounter(),
|
||||
seenStatus: SeenStatus.NotApplicable,
|
||||
sent_at: now,
|
||||
type: 'group',
|
||||
// TODO: DESKTOP-722
|
||||
} as unknown as MessageAttributesType);
|
||||
|
||||
const id = await window.Signal.Data.saveMessage(model.attributes, {
|
||||
ourUuid: window.textsecure.storage.user.getCheckedUuid().toString(),
|
||||
});
|
||||
model.set({ id });
|
||||
|
||||
const message = window.MessageController.register(model.id, model);
|
||||
void this.addSingleMessage(message);
|
||||
|
||||
const options = await getSendOptions(this.attributes);
|
||||
void message.send(
|
||||
handleMessageSend(
|
||||
messaging.leaveGroup(groupId, groupIdentifiers, options),
|
||||
{ messageIds: [], sendType: 'legacyGroupChange' }
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
async markRead(
|
||||
newestUnreadAt: number,
|
||||
options: {
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import {
|
||||
difference,
|
||||
isEmpty,
|
||||
isEqual,
|
||||
isNumber,
|
||||
|
@ -14,11 +13,9 @@ import {
|
|||
partition,
|
||||
pick,
|
||||
union,
|
||||
without,
|
||||
} from 'lodash';
|
||||
import type {
|
||||
CustomError,
|
||||
GroupV1Update,
|
||||
MessageAttributesType,
|
||||
MessageReactionType,
|
||||
QuotedMessageType,
|
||||
|
@ -85,7 +82,6 @@ import {
|
|||
isDirectConversation,
|
||||
isGroup,
|
||||
isGroupV1,
|
||||
isGroupV2,
|
||||
isMe,
|
||||
} from '../util/whatTypeOfConversation';
|
||||
import { handleMessageSend } from '../util/handleMessageSend';
|
||||
|
@ -145,11 +141,9 @@ import { notificationService } from '../services/notifications';
|
|||
import type { LinkPreviewType } from '../types/message/LinkPreviews';
|
||||
import * as log from '../logging/log';
|
||||
import * as Bytes from '../Bytes';
|
||||
import { computeHash } from '../Crypto';
|
||||
import { cleanupMessage, deleteMessageData } from '../util/cleanup';
|
||||
import {
|
||||
getContact,
|
||||
getContactId,
|
||||
getSource,
|
||||
getSourceUuid,
|
||||
isCustomError,
|
||||
|
@ -173,7 +167,6 @@ import { SeenStatus } from '../MessageSeenStatus';
|
|||
import { isNewReactionReplacingPrevious } from '../reactions/util';
|
||||
import { parseBoostBadgeListFromServer } from '../badges/parseBadgesFromServer';
|
||||
import { GiftBadgeStates } from '../components/conversation/Message';
|
||||
import { downloadAttachment } from '../util/downloadAttachment';
|
||||
import type { StickerWithHydratedData } from '../types/Stickers';
|
||||
import { getStringForConversationMerge } from '../util/getStringForConversationMerge';
|
||||
import {
|
||||
|
@ -2139,7 +2132,6 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
const sourceUuid = message.get('sourceUuid');
|
||||
const type = message.get('type');
|
||||
const conversationId = message.get('conversationId');
|
||||
const GROUP_TYPES = Proto.GroupContext.Type;
|
||||
|
||||
const fromContact = getContact(this.attributes);
|
||||
if (fromContact) {
|
||||
|
@ -2352,9 +2344,6 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
reason: 'handleDataMessage',
|
||||
})!;
|
||||
const hasGroupV2Prop = Boolean(initialMessage.groupV2);
|
||||
const isV1GroupUpdate =
|
||||
initialMessage.group &&
|
||||
initialMessage.group.type !== Proto.GroupContext.Type.DELIVER;
|
||||
|
||||
// Drop if from blocked user. Only GroupV2 messages should need to be dropped here.
|
||||
const isBlocked =
|
||||
|
@ -2397,7 +2386,6 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
type === 'incoming' &&
|
||||
!isDirectConversation(conversation.attributes) &&
|
||||
!hasGroupV2Prop &&
|
||||
!isV1GroupUpdate &&
|
||||
conversation.get('members') &&
|
||||
!areWeMember
|
||||
) {
|
||||
|
@ -2408,16 +2396,6 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
return;
|
||||
}
|
||||
|
||||
// Because GroupV1 messages can now be multiplexed into GroupV2 conversations, we
|
||||
// drop GroupV1 updates in GroupV2 groups.
|
||||
if (isV1GroupUpdate && isGroupV2(conversation.attributes)) {
|
||||
log.warn(
|
||||
`Received GroupV1 update in GroupV2 conversation ${conversation.idForLogging()}. Dropping.`
|
||||
);
|
||||
confirm();
|
||||
return;
|
||||
}
|
||||
|
||||
// Drop incoming messages to announcement only groups where sender is not admin
|
||||
if (
|
||||
conversation.get('announcementsOnly') &&
|
||||
|
@ -2615,160 +2593,10 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
}
|
||||
|
||||
if (isSupported) {
|
||||
let attributes = {
|
||||
const attributes = {
|
||||
...conversation.attributes,
|
||||
};
|
||||
|
||||
// GroupV1
|
||||
if (!hasGroupV2Prop && initialMessage.group) {
|
||||
const pendingGroupUpdate: GroupV1Update = {};
|
||||
|
||||
const memberConversations: Array<ConversationModel> =
|
||||
await Promise.all(
|
||||
initialMessage.group.membersE164.map((e164: string) =>
|
||||
window.ConversationController.getOrCreateAndWait(
|
||||
e164,
|
||||
'private'
|
||||
)
|
||||
)
|
||||
);
|
||||
const members = memberConversations.map(c => c.get('id'));
|
||||
attributes = {
|
||||
...attributes,
|
||||
type: 'group',
|
||||
groupId: initialMessage.group.id,
|
||||
};
|
||||
if (initialMessage.group.type === GROUP_TYPES.UPDATE) {
|
||||
attributes = {
|
||||
...attributes,
|
||||
name: initialMessage.group.name,
|
||||
members: union(members, conversation.get('members')),
|
||||
};
|
||||
|
||||
if (initialMessage.group.name !== conversation.get('name')) {
|
||||
pendingGroupUpdate.name = initialMessage.group.name;
|
||||
}
|
||||
|
||||
const avatarAttachment = initialMessage.group.avatar;
|
||||
|
||||
let downloadedAvatar;
|
||||
let hash;
|
||||
if (avatarAttachment) {
|
||||
try {
|
||||
downloadedAvatar = await downloadAttachment(avatarAttachment);
|
||||
|
||||
if (downloadedAvatar) {
|
||||
const loadedAttachment =
|
||||
await window.Signal.Migrations.loadAttachmentData(
|
||||
downloadedAvatar
|
||||
);
|
||||
|
||||
hash = computeHash(loadedAttachment.data);
|
||||
}
|
||||
} catch (err) {
|
||||
log.info(`${idLog}: group avatar download failed`);
|
||||
}
|
||||
}
|
||||
|
||||
const existingAvatar = conversation.get('avatar');
|
||||
|
||||
if (
|
||||
// Avatar added
|
||||
(!existingAvatar && avatarAttachment) ||
|
||||
// Avatar changed
|
||||
(existingAvatar && existingAvatar.hash !== hash) ||
|
||||
// Avatar removed
|
||||
(existingAvatar && !avatarAttachment)
|
||||
) {
|
||||
// Removes existing avatar from disk
|
||||
if (existingAvatar && existingAvatar.path) {
|
||||
await window.Signal.Migrations.deleteAttachmentData(
|
||||
existingAvatar.path
|
||||
);
|
||||
}
|
||||
|
||||
let avatar = null;
|
||||
if (downloadedAvatar && avatarAttachment != null) {
|
||||
const onDiskAttachment =
|
||||
await Attachment.migrateDataToFileSystem(downloadedAvatar, {
|
||||
writeNewAttachmentData:
|
||||
window.Signal.Migrations.writeNewAttachmentData,
|
||||
logger: log,
|
||||
});
|
||||
avatar = {
|
||||
...onDiskAttachment,
|
||||
hash,
|
||||
};
|
||||
}
|
||||
|
||||
if (!avatar) {
|
||||
attributes.avatar = avatar;
|
||||
} else {
|
||||
const { url, path } = avatar;
|
||||
strictAssert(url, 'Avatar needs url');
|
||||
strictAssert(path, 'Avatar needs path');
|
||||
attributes.avatar = {
|
||||
url,
|
||||
path,
|
||||
...avatar,
|
||||
};
|
||||
}
|
||||
|
||||
pendingGroupUpdate.avatarUpdated = true;
|
||||
} else {
|
||||
log.info(
|
||||
`${idLog}: Group avatar hash matched; not replacing group avatar`
|
||||
);
|
||||
}
|
||||
|
||||
const differentMembers = difference(
|
||||
members,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
conversation.get('members')!
|
||||
);
|
||||
if (differentMembers.length > 0) {
|
||||
// Because GroupV1 groups are based on e164 only
|
||||
const maybeE164s = map(differentMembers, id =>
|
||||
window.ConversationController.get(id)?.get('e164')
|
||||
);
|
||||
const e164s = filter(maybeE164s, isNotNil);
|
||||
pendingGroupUpdate.joined = [...e164s];
|
||||
}
|
||||
if (conversation.get('left')) {
|
||||
log.warn('re-added to a left group');
|
||||
attributes.left = false;
|
||||
conversation.set({ addedBy: getContactId(message.attributes) });
|
||||
}
|
||||
} else if (initialMessage.group.type === GROUP_TYPES.QUIT) {
|
||||
const inGroup = Boolean(
|
||||
sender &&
|
||||
(conversation.get('members') || []).includes(sender.id)
|
||||
);
|
||||
if (!inGroup) {
|
||||
const senderString = sender ? sender.idForLogging() : null;
|
||||
log.info(
|
||||
`${idLog}: Got 'left' message from someone not in group: ${senderString}. Dropping.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isMe(sender.attributes)) {
|
||||
attributes.left = true;
|
||||
pendingGroupUpdate.left = 'You';
|
||||
} else {
|
||||
pendingGroupUpdate.left = sender.get('id');
|
||||
}
|
||||
attributes.members = without(
|
||||
conversation.get('members'),
|
||||
sender.get('id')
|
||||
);
|
||||
}
|
||||
|
||||
if (!isEmpty(pendingGroupUpdate)) {
|
||||
message.set('group_update', pendingGroupUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
// Drop empty messages after. This needs to happen after the initial
|
||||
// message.set call and after GroupV1 processing to make sure all possible
|
||||
// properties are set before we determine that a message is empty.
|
||||
|
|
|
@ -32,10 +32,6 @@ const PROCESSED_ATTACHMENT: ProcessedAttachment = {
|
|||
size: 34,
|
||||
};
|
||||
|
||||
const GROUP_ID = new Uint8Array([0x68, 0x65, 0x79]);
|
||||
|
||||
const DERIVED_GROUPV2_ID = '7qQUi8Wa6Jm3Rl+l63saATGeciEqokbHpP+lV3F5t9o=';
|
||||
|
||||
describe('processDataMessage', () => {
|
||||
const check = (message: Proto.IDataMessage) =>
|
||||
processDataMessage(
|
||||
|
@ -85,59 +81,6 @@ describe('processDataMessage', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('should process group context UPDATE/QUIT message', () => {
|
||||
const { UPDATE, QUIT } = Proto.GroupContext.Type;
|
||||
|
||||
for (const type of [UPDATE, QUIT]) {
|
||||
const out = check({
|
||||
body: 'should be deleted',
|
||||
attachments: [UNPROCESSED_ATTACHMENT],
|
||||
group: {
|
||||
id: GROUP_ID,
|
||||
name: 'Group',
|
||||
avatar: UNPROCESSED_ATTACHMENT,
|
||||
type,
|
||||
membersE164: ['+1'],
|
||||
},
|
||||
});
|
||||
|
||||
assert.isUndefined(out.body);
|
||||
assert.strictEqual(out.attachments.length, 0);
|
||||
assert.deepStrictEqual(out.group, {
|
||||
id: 'hey',
|
||||
name: 'Group',
|
||||
avatar: PROCESSED_ATTACHMENT,
|
||||
type,
|
||||
membersE164: ['+1'],
|
||||
derivedGroupV2Id: DERIVED_GROUPV2_ID,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('should process group context DELIVER message', () => {
|
||||
const out = check({
|
||||
body: 'should not be deleted',
|
||||
attachments: [UNPROCESSED_ATTACHMENT],
|
||||
group: {
|
||||
id: GROUP_ID,
|
||||
name: 'should be deleted',
|
||||
membersE164: ['should be deleted'],
|
||||
type: Proto.GroupContext.Type.DELIVER,
|
||||
},
|
||||
});
|
||||
|
||||
assert.strictEqual(out.body, 'should not be deleted');
|
||||
assert.strictEqual(out.attachments.length, 1);
|
||||
assert.deepStrictEqual(out.group, {
|
||||
id: 'hey',
|
||||
type: Proto.GroupContext.Type.DELIVER,
|
||||
membersE164: [],
|
||||
derivedGroupV2Id: DERIVED_GROUPV2_ID,
|
||||
avatar: undefined,
|
||||
name: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should process groupv2 context', () => {
|
||||
const out = check({
|
||||
groupV2: {
|
||||
|
@ -312,15 +255,10 @@ describe('processDataMessage', () => {
|
|||
const out = check({
|
||||
flags: FLAGS.END_SESSION,
|
||||
body: 'should be deleted',
|
||||
group: {
|
||||
id: GROUP_ID,
|
||||
type: Proto.GroupContext.Type.DELIVER,
|
||||
},
|
||||
attachments: [UNPROCESSED_ATTACHMENT],
|
||||
});
|
||||
|
||||
assert.isUndefined(out.body);
|
||||
assert.isUndefined(out.group);
|
||||
assert.deepStrictEqual(out.attachments, []);
|
||||
});
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ import { parseIntOrThrow } from '../util/parseIntOrThrow';
|
|||
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
|
||||
import { Zone } from '../util/Zone';
|
||||
import { DurationInSeconds } from '../util/durations';
|
||||
import { deriveMasterKeyFromGroupV1, bytesToUuid } from '../Crypto';
|
||||
import { bytesToUuid } from '../Crypto';
|
||||
import type { DownloadedAttachmentType } from '../types/Attachment';
|
||||
import { Address } from '../types/Address';
|
||||
import { QualifiedAddress } from '../types/QualifiedAddress';
|
||||
|
@ -127,7 +127,6 @@ import { inspectUnknownFieldTags } from '../util/inspectProtobufs';
|
|||
import { incrementMessageCounter } from '../util/incrementMessageCounter';
|
||||
import { filterAndClean } from '../types/BodyRange';
|
||||
|
||||
const GROUPV1_ID_LENGTH = 16;
|
||||
const GROUPV2_ID_LENGTH = 32;
|
||||
const RETRY_TIMEOUT = 2 * 60 * 1000;
|
||||
|
||||
|
@ -2007,19 +2006,8 @@ export default class MessageReceiver
|
|||
const message = this.processDecrypted(envelope, msg);
|
||||
const groupId = this.getProcessedGroupId(message);
|
||||
const isBlocked = groupId ? this.isGroupBlocked(groupId) : false;
|
||||
const { source, sourceUuid } = envelope;
|
||||
const ourE164 = this.storage.user.getNumber();
|
||||
const ourUuid = this.storage.user.getCheckedUuid().toString();
|
||||
const isMe =
|
||||
(source && ourE164 && source === ourE164) ||
|
||||
(sourceUuid && ourUuid && sourceUuid === ourUuid);
|
||||
const isLeavingGroup = Boolean(
|
||||
!message.groupV2 &&
|
||||
message.group &&
|
||||
message.group.type === Proto.GroupContext.Type.QUIT
|
||||
);
|
||||
|
||||
if (groupId && isBlocked && !(isMe && isLeavingGroup)) {
|
||||
if (groupId && isBlocked) {
|
||||
log.warn(
|
||||
`Message ${getEnvelopeId(envelope)} ignored; destined for blocked group`
|
||||
);
|
||||
|
@ -2282,19 +2270,8 @@ export default class MessageReceiver
|
|||
const message = this.processDecrypted(envelope, msg.dataMessage);
|
||||
const groupId = this.getProcessedGroupId(message);
|
||||
const isBlocked = groupId ? this.isGroupBlocked(groupId) : false;
|
||||
const { source, sourceUuid } = envelope;
|
||||
const ourE164 = this.storage.user.getNumber();
|
||||
const ourUuid = this.storage.user.getCheckedUuid().toString();
|
||||
const isMe =
|
||||
(source && ourE164 && source === ourE164) ||
|
||||
(sourceUuid && ourUuid && sourceUuid === ourUuid);
|
||||
const isLeavingGroup = Boolean(
|
||||
!message.groupV2 &&
|
||||
message.group &&
|
||||
message.group.type === Proto.GroupContext.Type.QUIT
|
||||
);
|
||||
|
||||
if (groupId && isBlocked && !(isMe && isLeavingGroup)) {
|
||||
if (groupId && isBlocked) {
|
||||
log.warn(
|
||||
`Message ${getEnvelopeId(envelope)} ignored; destined for blocked group`
|
||||
);
|
||||
|
@ -2354,8 +2331,6 @@ export default class MessageReceiver
|
|||
return undefined;
|
||||
}
|
||||
|
||||
this.checkGroupV1Data(msg);
|
||||
|
||||
if (msg.flags && msg.flags & Proto.DataMessage.Flags.END_SESSION) {
|
||||
p = this.handleEndSession(envelope, new UUID(destination));
|
||||
}
|
||||
|
@ -2393,8 +2368,6 @@ export default class MessageReceiver
|
|||
msg.flags & Proto.DataMessage.Flags.EXPIRATION_TIMER_UPDATE
|
||||
) {
|
||||
type = 'expirationTimerUpdate';
|
||||
} else if (msg.group) {
|
||||
type = 'legacyGroupChange';
|
||||
}
|
||||
// Note: other data messages without any of these attributes will fall into the
|
||||
// 'message' bucket - like stickers, gift badges, etc.
|
||||
|
@ -2404,19 +2377,8 @@ export default class MessageReceiver
|
|||
const message = this.processDecrypted(envelope, msg);
|
||||
const groupId = this.getProcessedGroupId(message);
|
||||
const isBlocked = groupId ? this.isGroupBlocked(groupId) : false;
|
||||
const { source, sourceUuid } = envelope;
|
||||
const ourE164 = this.storage.user.getNumber();
|
||||
const ourUuid = this.storage.user.getCheckedUuid().toString();
|
||||
const isMe =
|
||||
(source && ourE164 && source === ourE164) ||
|
||||
(sourceUuid && ourUuid && sourceUuid === ourUuid);
|
||||
const isLeavingGroup = Boolean(
|
||||
!message.groupV2 &&
|
||||
message.group &&
|
||||
message.group.type === Proto.GroupContext.Type.QUIT
|
||||
);
|
||||
|
||||
if (groupId && isBlocked && !(isMe && isLeavingGroup)) {
|
||||
if (groupId && isBlocked) {
|
||||
log.warn(
|
||||
`Message ${getEnvelopeId(envelope)} ignored; destined for blocked group`
|
||||
);
|
||||
|
@ -2780,17 +2742,11 @@ export default class MessageReceiver
|
|||
|
||||
const { groupId, timestamp, action } = typingMessage;
|
||||
|
||||
let groupIdString: string | undefined;
|
||||
let groupV2IdString: string | undefined;
|
||||
if (groupId && groupId.byteLength > 0) {
|
||||
if (groupId.byteLength === GROUPV1_ID_LENGTH) {
|
||||
groupIdString = Bytes.toBinary(groupId);
|
||||
groupV2IdString = this.deriveGroupV2FromV1(groupId);
|
||||
} else if (groupId.byteLength === GROUPV2_ID_LENGTH) {
|
||||
groupV2IdString = Bytes.toBase64(groupId);
|
||||
} else {
|
||||
log.error('handleTypingMessage: Received invalid groupId value');
|
||||
}
|
||||
if (groupId && groupId.byteLength === GROUPV2_ID_LENGTH) {
|
||||
groupV2IdString = Bytes.toBase64(groupId);
|
||||
} else {
|
||||
log.error('handleTypingMessage: Received invalid groupId value');
|
||||
}
|
||||
|
||||
this.dispatchEvent(
|
||||
|
@ -2799,13 +2755,11 @@ export default class MessageReceiver
|
|||
senderUuid: envelope.sourceUuid,
|
||||
senderDevice: envelope.sourceDevice,
|
||||
typing: {
|
||||
groupV2Id: groupV2IdString,
|
||||
typingMessage,
|
||||
timestamp: timestamp?.toNumber() ?? Date.now(),
|
||||
started: action === Proto.TypingMessage.Action.STARTED,
|
||||
stopped: action === Proto.TypingMessage.Action.STOPPED,
|
||||
|
||||
groupId: groupIdString,
|
||||
groupV2Id: groupV2IdString,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
@ -2823,22 +2777,7 @@ export default class MessageReceiver
|
|||
message: Proto.IDataMessage,
|
||||
envelope: ProcessedEnvelope
|
||||
): boolean {
|
||||
const { group, groupV2 } = message;
|
||||
|
||||
if (group) {
|
||||
const { id } = group;
|
||||
strictAssert(id, 'Group data has no id');
|
||||
const isInvalid = id.byteLength !== GROUPV1_ID_LENGTH;
|
||||
|
||||
if (isInvalid) {
|
||||
log.info(
|
||||
'isInvalidGroupData: invalid GroupV1 message from',
|
||||
getEnvelopeId(envelope)
|
||||
);
|
||||
}
|
||||
|
||||
return isInvalid;
|
||||
}
|
||||
const { groupV2 } = message;
|
||||
|
||||
if (groupV2) {
|
||||
const { masterKey } = groupV2;
|
||||
|
@ -2857,46 +2796,12 @@ export default class MessageReceiver
|
|||
return false;
|
||||
}
|
||||
|
||||
private deriveGroupV2FromV1(groupId: Uint8Array): string {
|
||||
if (groupId.byteLength !== GROUPV1_ID_LENGTH) {
|
||||
throw new Error(
|
||||
`deriveGroupV2FromV1: had id with wrong byteLength: ${groupId.byteLength}`
|
||||
);
|
||||
}
|
||||
const masterKey = deriveMasterKeyFromGroupV1(groupId);
|
||||
const data = deriveGroupFields(masterKey);
|
||||
|
||||
return Bytes.toBase64(data.id);
|
||||
}
|
||||
|
||||
private checkGroupV1Data(message: Readonly<Proto.IDataMessage>): void {
|
||||
const { group } = message;
|
||||
|
||||
if (!group) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!group.id) {
|
||||
throw new Error('deriveGroupV1Data: had falsey id');
|
||||
}
|
||||
|
||||
const { id } = group;
|
||||
if (id.byteLength !== GROUPV1_ID_LENGTH) {
|
||||
throw new Error(
|
||||
`deriveGroupV1Data: had id with wrong byteLength: ${id.byteLength}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private getProcessedGroupId(
|
||||
message: ProcessedDataMessage
|
||||
): string | undefined {
|
||||
if (message.groupV2) {
|
||||
return message.groupV2.id;
|
||||
}
|
||||
if (message.group && message.group.id) {
|
||||
return message.group.id;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
@ -2906,9 +2811,6 @@ export default class MessageReceiver
|
|||
const { id } = deriveGroupFields(message.groupV2.masterKey);
|
||||
return Bytes.toBase64(id);
|
||||
}
|
||||
if (message.group && message.group.id) {
|
||||
return Bytes.toBinary(message.group.id);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
@ -2917,10 +2819,6 @@ export default class MessageReceiver
|
|||
if (sentMessage.message && sentMessage.message.groupV2) {
|
||||
return `groupv2(${this.getGroupId(sentMessage.message)})`;
|
||||
}
|
||||
if (sentMessage.message && sentMessage.message.group) {
|
||||
strictAssert(sentMessage.message.group.id, 'group without id');
|
||||
return `group(${this.getGroupId(sentMessage.message)})`;
|
||||
}
|
||||
return sentMessage.destination || sentMessage.destinationUuid;
|
||||
}
|
||||
|
||||
|
@ -2998,8 +2896,6 @@ export default class MessageReceiver
|
|||
return;
|
||||
}
|
||||
|
||||
this.checkGroupV1Data(sentMessage.message);
|
||||
|
||||
strictAssert(sentMessage.timestamp, 'sent message without timestamp');
|
||||
|
||||
log.info(
|
||||
|
@ -3192,19 +3088,13 @@ export default class MessageReceiver
|
|||
|
||||
const { groupId } = sync;
|
||||
|
||||
let groupIdString: string | undefined;
|
||||
let groupV2IdString: string | undefined;
|
||||
if (groupId && groupId.byteLength > 0) {
|
||||
if (groupId.byteLength === GROUPV1_ID_LENGTH) {
|
||||
groupIdString = Bytes.toBinary(groupId);
|
||||
groupV2IdString = this.deriveGroupV2FromV1(groupId);
|
||||
} else if (groupId.byteLength === GROUPV2_ID_LENGTH) {
|
||||
groupV2IdString = Bytes.toBase64(groupId);
|
||||
} else {
|
||||
this.removeFromCache(envelope);
|
||||
log.error('Received message request with invalid groupId');
|
||||
return undefined;
|
||||
}
|
||||
if (groupId && groupId.byteLength === GROUPV2_ID_LENGTH) {
|
||||
groupV2IdString = Bytes.toBase64(groupId);
|
||||
} else {
|
||||
this.removeFromCache(envelope);
|
||||
log.error('Received message request with invalid groupId');
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const ev = new MessageRequestResponseEvent(
|
||||
|
@ -3217,7 +3107,6 @@ export default class MessageReceiver
|
|||
)
|
||||
: undefined,
|
||||
messageRequestResponseType: sync.type,
|
||||
groupId: groupIdString,
|
||||
groupV2Id: groupV2IdString,
|
||||
},
|
||||
this.removeFromCache.bind(this, envelope)
|
||||
|
@ -3583,14 +3472,10 @@ export default class MessageReceiver
|
|||
|
||||
if (blocked.groupIds) {
|
||||
const previous = this.storage.get('blocked-groups', []);
|
||||
const groupV1Ids: Array<string> = [];
|
||||
const groupIds: Array<string> = [];
|
||||
|
||||
blocked.groupIds.forEach(groupId => {
|
||||
if (groupId.byteLength === GROUPV1_ID_LENGTH) {
|
||||
groupV1Ids.push(Bytes.toBinary(groupId));
|
||||
groupIds.push(this.deriveGroupV2FromV1(groupId));
|
||||
} else if (groupId.byteLength === GROUPV2_ID_LENGTH) {
|
||||
if (groupId.byteLength === GROUPV2_ID_LENGTH) {
|
||||
groupIds.push(Bytes.toBase64(groupId));
|
||||
} else {
|
||||
log.error('handleBlocked: Received invalid groupId value');
|
||||
|
@ -3598,18 +3483,15 @@ export default class MessageReceiver
|
|||
});
|
||||
log.info(
|
||||
'handleBlocked: Blocking these groups - v2:',
|
||||
groupIds.map(groupId => `groupv2(${groupId})`),
|
||||
'v1:',
|
||||
groupV1Ids.map(groupId => `group(${groupId})`)
|
||||
groupIds.map(groupId => `groupv2(${groupId})`)
|
||||
);
|
||||
|
||||
const ids = [...groupIds, ...groupV1Ids];
|
||||
await this.storage.put('blocked-groups', ids);
|
||||
await this.storage.put('blocked-groups', groupIds);
|
||||
|
||||
if (!areArraysMatchingSets(previous, ids)) {
|
||||
if (!areArraysMatchingSets(previous, groupIds)) {
|
||||
changed = true;
|
||||
allIdentifiers.push(...previous);
|
||||
allIdentifiers.push(...ids);
|
||||
allIdentifiers.push(...groupIds);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -103,10 +103,6 @@ export type GroupV2InfoType = {
|
|||
revision: number;
|
||||
members: ReadonlyArray<string>;
|
||||
};
|
||||
export type GroupV1InfoType = {
|
||||
id: string;
|
||||
members: ReadonlyArray<string>;
|
||||
};
|
||||
|
||||
type GroupCallUpdateType = {
|
||||
eraId: string;
|
||||
|
@ -207,7 +203,6 @@ export type GroupSendOptionsType = {
|
|||
expireTimer?: DurationInSeconds;
|
||||
flags?: number;
|
||||
groupCallUpdate?: GroupCallUpdateType;
|
||||
groupV1?: GroupV1InfoType;
|
||||
groupV2?: GroupV2InfoType;
|
||||
messageText?: string;
|
||||
preview?: ReadonlyArray<LinkPreviewType>;
|
||||
|
@ -381,10 +376,6 @@ class Message {
|
|||
proto.groupV2.masterKey = this.groupV2.masterKey;
|
||||
proto.groupV2.revision = this.groupV2.revision;
|
||||
proto.groupV2.groupChange = this.groupV2.groupChange || null;
|
||||
} else if (this.group) {
|
||||
proto.group = new Proto.GroupContext();
|
||||
proto.group.id = Bytes.fromString(this.group.id);
|
||||
proto.group.type = this.group.type;
|
||||
}
|
||||
if (this.sticker) {
|
||||
proto.sticker = new Proto.DataMessage.Sticker();
|
||||
|
@ -1106,7 +1097,6 @@ export default class MessageSender {
|
|||
expireTimer,
|
||||
flags,
|
||||
groupCallUpdate,
|
||||
groupV1,
|
||||
groupV2,
|
||||
messageText,
|
||||
preview,
|
||||
|
@ -1118,16 +1108,16 @@ export default class MessageSender {
|
|||
timestamp,
|
||||
} = options;
|
||||
|
||||
if (!groupV1 && !groupV2) {
|
||||
if (!groupV2) {
|
||||
throw new Error(
|
||||
'getAttrsFromGroupOptions: Neither group1 nor groupv2 information provided!'
|
||||
'getAttrsFromGroupOptions: No groupv2 information provided!'
|
||||
);
|
||||
}
|
||||
|
||||
const myE164 = window.textsecure.storage.user.getNumber();
|
||||
const myUuid = window.textsecure.storage.user.getUuid()?.toString();
|
||||
|
||||
const groupMembers = groupV2?.members || groupV1?.members || [];
|
||||
const groupMembers = groupV2?.members || [];
|
||||
|
||||
// We should always have a UUID but have this check just in case we don't.
|
||||
let isNotMe: (recipient: string) => boolean;
|
||||
|
@ -1158,12 +1148,6 @@ export default class MessageSender {
|
|||
flags,
|
||||
groupCallUpdate,
|
||||
groupV2,
|
||||
group: groupV1
|
||||
? {
|
||||
id: groupV1.id,
|
||||
type: Proto.GroupContext.Type.DELIVER,
|
||||
}
|
||||
: undefined,
|
||||
preview,
|
||||
profileKey,
|
||||
quote,
|
||||
|
@ -2424,50 +2408,6 @@ export default class MessageSender {
|
|||
});
|
||||
}
|
||||
|
||||
// GroupV1-only functions; not to be used in the future
|
||||
|
||||
async leaveGroup(
|
||||
groupId: string,
|
||||
groupIdentifiers: Array<string>,
|
||||
options?: SendOptionsType
|
||||
): Promise<CallbackResultType> {
|
||||
const timestamp = Date.now();
|
||||
const proto = new Proto.Content({
|
||||
dataMessage: {
|
||||
group: {
|
||||
id: Bytes.fromString(groupId),
|
||||
type: Proto.GroupContext.Type.QUIT,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
const contentHint = ContentHint.RESENDABLE;
|
||||
const sendLogCallback =
|
||||
groupIdentifiers.length > 1
|
||||
? this.makeSendLogCallback({
|
||||
contentHint,
|
||||
proto: Buffer.from(Proto.Content.encode(proto).finish()),
|
||||
sendType: 'legacyGroupChange',
|
||||
timestamp,
|
||||
urgent: false,
|
||||
hasPniSignatureMessage: false,
|
||||
})
|
||||
: undefined;
|
||||
|
||||
return this.sendGroupProto({
|
||||
contentHint,
|
||||
groupId: undefined, // only for GV2 ids
|
||||
options,
|
||||
proto,
|
||||
recipients: groupIdentifiers,
|
||||
sendLogCallback,
|
||||
timestamp,
|
||||
urgent: false,
|
||||
});
|
||||
}
|
||||
|
||||
// Simple pass-throughs
|
||||
|
||||
// Note: instead of updating these functions, or adding new ones, remove these and go
|
||||
|
|
12
ts/textsecure/Types.d.ts
vendored
12
ts/textsecure/Types.d.ts
vendored
|
@ -118,17 +118,6 @@ export type ProcessedAttachment = {
|
|||
textAttachment?: Omit<TextAttachmentType, 'preview'>;
|
||||
};
|
||||
|
||||
export type ProcessedGroupContext = {
|
||||
id: string;
|
||||
type: Proto.GroupContext.Type;
|
||||
name?: string;
|
||||
membersE164: ReadonlyArray<string>;
|
||||
avatar?: ProcessedAttachment;
|
||||
|
||||
// Computed fields
|
||||
derivedGroupV2Id: string;
|
||||
};
|
||||
|
||||
export type ProcessedGroupV2Context = {
|
||||
masterKey: string;
|
||||
revision?: number;
|
||||
|
@ -208,7 +197,6 @@ export type ProcessedGiftBadge = {
|
|||
export type ProcessedDataMessage = {
|
||||
body?: string;
|
||||
attachments: ReadonlyArray<ProcessedAttachment>;
|
||||
group?: ProcessedGroupContext;
|
||||
groupV2?: ProcessedGroupV2Context;
|
||||
flags: number;
|
||||
expireTimer: DurationInSeconds;
|
||||
|
|
|
@ -10,12 +10,10 @@ import { dropNull, shallowDropNull } from '../util/dropNull';
|
|||
import { SignalService as Proto } from '../protobuf';
|
||||
import { deriveGroupFields } from '../groups';
|
||||
import * as Bytes from '../Bytes';
|
||||
import { deriveMasterKeyFromGroupV1 } from '../Crypto';
|
||||
|
||||
import type {
|
||||
ProcessedAttachment,
|
||||
ProcessedDataMessage,
|
||||
ProcessedGroupContext,
|
||||
ProcessedGroupV2Context,
|
||||
ProcessedQuote,
|
||||
ProcessedContact,
|
||||
|
@ -25,7 +23,6 @@ import type {
|
|||
ProcessedDelete,
|
||||
ProcessedGiftBadge,
|
||||
} from './Types.d';
|
||||
import { WarnOnlyError } from './Errors';
|
||||
import { GiftBadgeStates } from '../components/conversation/Message';
|
||||
import { APPLICATION_OCTET_STREAM, stringToMIMEType } from '../types/MIME';
|
||||
import { SECOND, DurationInSeconds } from '../util/durations';
|
||||
|
@ -71,39 +68,6 @@ export function processAttachment(
|
|||
};
|
||||
}
|
||||
|
||||
function processGroupContext(
|
||||
group?: Proto.IGroupContext | null
|
||||
): ProcessedGroupContext | undefined {
|
||||
if (!group) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
strictAssert(group.id, 'group context without id');
|
||||
strictAssert(group.type != null, 'group context without type');
|
||||
|
||||
const masterKey = deriveMasterKeyFromGroupV1(group.id);
|
||||
const data = deriveGroupFields(masterKey);
|
||||
|
||||
const derivedGroupV2Id = Bytes.toBase64(data.id);
|
||||
|
||||
const result: ProcessedGroupContext = {
|
||||
id: Bytes.toBinary(group.id),
|
||||
type: group.type,
|
||||
name: dropNull(group.name),
|
||||
membersE164: group.membersE164 ?? [],
|
||||
avatar: processAttachment(group.avatar),
|
||||
derivedGroupV2Id,
|
||||
};
|
||||
|
||||
if (result.type === Proto.GroupContext.Type.DELIVER) {
|
||||
result.name = undefined;
|
||||
result.membersE164 = [];
|
||||
result.avatar = undefined;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function processGroupV2Context(
|
||||
groupV2?: Proto.IGroupContextV2 | null
|
||||
): ProcessedGroupV2Context | undefined {
|
||||
|
@ -331,7 +295,6 @@ export function processDataMessage(
|
|||
attachments: (message.attachments ?? []).map(
|
||||
(attachment: Proto.IAttachmentPointer) => processAttachment(attachment)
|
||||
),
|
||||
group: processGroupContext(message.group),
|
||||
groupV2: processGroupV2Context(message.groupV2),
|
||||
flags: message.flags ?? 0,
|
||||
expireTimer: DurationInSeconds.fromSeconds(message.expireTimer ?? 0),
|
||||
|
@ -375,7 +338,6 @@ export function processDataMessage(
|
|||
if (isEndSession) {
|
||||
result.body = undefined;
|
||||
result.attachments = [];
|
||||
result.group = undefined;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -389,27 +351,6 @@ export function processDataMessage(
|
|||
throw new Error(`Unknown flags in message: ${result.flags}`);
|
||||
}
|
||||
|
||||
if (result.group) {
|
||||
switch (result.group.type) {
|
||||
case Proto.GroupContext.Type.UPDATE:
|
||||
result.body = undefined;
|
||||
result.attachments = [];
|
||||
break;
|
||||
case Proto.GroupContext.Type.QUIT:
|
||||
result.body = undefined;
|
||||
result.attachments = [];
|
||||
break;
|
||||
case Proto.GroupContext.Type.DELIVER:
|
||||
// Cleaned up in `processGroupContext`
|
||||
break;
|
||||
default: {
|
||||
throw new WarnOnlyError(
|
||||
`Unknown group message type: ${result.group.type}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const attachmentCount = result.attachments.length;
|
||||
if (attachmentCount > ATTACHMENT_MAX) {
|
||||
throw new Error(
|
||||
|
|
|
@ -859,9 +859,6 @@ function getRecipients(options: GroupSendOptionsType): ReadonlyArray<string> {
|
|||
if (options.groupV2) {
|
||||
return options.groupV2.members;
|
||||
}
|
||||
if (options.groupV1) {
|
||||
return options.groupV1.members;
|
||||
}
|
||||
|
||||
throw new Error('getRecipients: Unable to extract recipients!');
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue