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;
|
optional string body = 1;
|
||||||
repeated AttachmentPointer attachments = 2;
|
repeated AttachmentPointer attachments = 2;
|
||||||
optional GroupContext group = 3;
|
reserved /*groupV1*/ 3;
|
||||||
optional GroupContextV2 groupV2 = 15;
|
optional GroupContextV2 groupV2 = 15;
|
||||||
optional uint32 flags = 4;
|
optional uint32 flags = 4;
|
||||||
optional uint32 expireTimer = 5;
|
optional uint32 expireTimer = 5;
|
||||||
|
@ -664,23 +664,6 @@ message AttachmentPointer {
|
||||||
// Next ID: 16
|
// 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 {
|
message GroupContextV2 {
|
||||||
optional bytes masterKey = 1;
|
optional bytes masterKey = 1;
|
||||||
optional uint32 revision = 2;
|
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
|
// Note: this type of message is automatically removed from cache in MessageReceiver
|
||||||
|
|
||||||
const { typing, sender, senderUuid, senderDevice } = ev;
|
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
|
// We don't do anything with incoming typing messages if the setting is disabled
|
||||||
if (!window.storage.get('typingIndicators')) {
|
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
|
// We multiplex between GV1/GV2 groups here, but we don't kick off migrations
|
||||||
if (groupV2Id) {
|
if (groupV2Id) {
|
||||||
conversation = window.ConversationController.get(groupV2Id);
|
conversation = window.ConversationController.get(groupV2Id);
|
||||||
}
|
} else {
|
||||||
if (!conversation && groupId) {
|
|
||||||
conversation = window.ConversationController.get(groupId);
|
|
||||||
}
|
|
||||||
if (!groupV2Id && !groupId) {
|
|
||||||
conversation = senderConversation;
|
conversation = senderConversation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2737,7 +2733,7 @@ export async function startApp(): Promise<void> {
|
||||||
}
|
}
|
||||||
if (!conversation) {
|
if (!conversation) {
|
||||||
log.warn(
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -2988,8 +2984,6 @@ export async function startApp(): Promise<void> {
|
||||||
|
|
||||||
const messageDescriptor = getMessageDescriptor({
|
const messageDescriptor = getMessageDescriptor({
|
||||||
message: data.message,
|
message: data.message,
|
||||||
source: data.source,
|
|
||||||
sourceUuid: data.sourceUuid,
|
|
||||||
// 'message' event: for 1:1 converations, the conversation is same as sender
|
// 'message' event: for 1:1 converations, the conversation is same as sender
|
||||||
destination: data.source,
|
destination: data.source,
|
||||||
destinationUuid: data.sourceUuid,
|
destinationUuid: data.sourceUuid,
|
||||||
|
@ -3290,18 +3284,13 @@ export async function startApp(): Promise<void> {
|
||||||
return new window.Whisper.Message(partialMessage);
|
return new window.Whisper.Message(partialMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Works with 'sent' and 'message' data sent from MessageReceiver, with a little massage
|
// Works with 'sent' and 'message' data sent from MessageReceiver
|
||||||
// at callsites to make sure both source and destination are populated.
|
|
||||||
const getMessageDescriptor = ({
|
const getMessageDescriptor = ({
|
||||||
message,
|
message,
|
||||||
source,
|
|
||||||
sourceUuid,
|
|
||||||
destination,
|
destination,
|
||||||
destinationUuid,
|
destinationUuid,
|
||||||
}: {
|
}: {
|
||||||
message: ProcessedDataMessage;
|
message: ProcessedDataMessage;
|
||||||
source?: string;
|
|
||||||
sourceUuid?: string;
|
|
||||||
destination?: string;
|
destination?: string;
|
||||||
destinationUuid?: string;
|
destinationUuid?: string;
|
||||||
}): MessageDescriptor => {
|
}): MessageDescriptor => {
|
||||||
|
@ -3342,44 +3331,6 @@ export async function startApp(): Promise<void> {
|
||||||
id: conversationId,
|
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({
|
const conversation = window.ConversationController.lookupOrCreate({
|
||||||
uuid: destinationUuid,
|
uuid: destinationUuid,
|
||||||
|
@ -3406,10 +3357,6 @@ export async function startApp(): Promise<void> {
|
||||||
|
|
||||||
const messageDescriptor = getMessageDescriptor({
|
const messageDescriptor = getMessageDescriptor({
|
||||||
...data,
|
...data,
|
||||||
|
|
||||||
// 'sent' event: the sender is always us!
|
|
||||||
source,
|
|
||||||
sourceUuid,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const { PROFILE_KEY_UPDATE } = Proto.DataMessage.Flags;
|
const { PROFILE_KEY_UPDATE } = Proto.DataMessage.Flags;
|
||||||
|
@ -3765,18 +3712,12 @@ export async function startApp(): Promise<void> {
|
||||||
function onMessageRequestResponse(ev: MessageRequestResponseEvent): void {
|
function onMessageRequestResponse(ev: MessageRequestResponseEvent): void {
|
||||||
ev.confirm();
|
ev.confirm();
|
||||||
|
|
||||||
const {
|
const { threadE164, threadUuid, groupV2Id, messageRequestResponseType } =
|
||||||
threadE164,
|
ev;
|
||||||
threadUuid,
|
|
||||||
groupId,
|
|
||||||
groupV2Id,
|
|
||||||
messageRequestResponseType,
|
|
||||||
} = ev;
|
|
||||||
|
|
||||||
log.info('onMessageRequestResponse', {
|
log.info('onMessageRequestResponse', {
|
||||||
threadE164,
|
threadE164,
|
||||||
threadUuid,
|
threadUuid,
|
||||||
groupId: `group(${groupId})`,
|
|
||||||
groupV2Id: `groupv2(${groupV2Id})`,
|
groupV2Id: `groupv2(${groupV2Id})`,
|
||||||
messageRequestResponseType,
|
messageRequestResponseType,
|
||||||
});
|
});
|
||||||
|
@ -3789,7 +3730,6 @@ export async function startApp(): Promise<void> {
|
||||||
const attributes: MessageRequestAttributesType = {
|
const attributes: MessageRequestAttributesType = {
|
||||||
threadE164,
|
threadE164,
|
||||||
threadUuid,
|
threadUuid,
|
||||||
groupId,
|
|
||||||
groupV2Id,
|
groupV2Id,
|
||||||
type: messageRequestResponseType,
|
type: messageRequestResponseType,
|
||||||
};
|
};
|
||||||
|
|
|
@ -404,6 +404,44 @@ export class ConversationHeader extends React.Component<PropsType, StateType> {
|
||||||
|
|
||||||
const hasGV2AdminEnabled = isGroup && groupVersion === 2;
|
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 => {
|
const isActiveExpireTimer = (value: number): boolean => {
|
||||||
if (!expireTimer) {
|
if (!expireTimer) {
|
||||||
return value === 0;
|
return value === 0;
|
||||||
|
@ -487,15 +525,6 @@ export class ConversationHeader extends React.Component<PropsType, StateType> {
|
||||||
: i18n('icu:showConversationDetails--direct')}
|
: i18n('icu:showConversationDetails--direct')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
) : null}
|
) : null}
|
||||||
{isGroup && !hasGV2AdminEnabled ? (
|
|
||||||
<MenuItem
|
|
||||||
onClick={() =>
|
|
||||||
pushPanelForConversation({ type: PanelType.GroupV1Members })
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{i18n('icu:showMembers')}
|
|
||||||
</MenuItem>
|
|
||||||
) : null}
|
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => pushPanelForConversation({ type: PanelType.AllMedia })}
|
onClick={() => pushPanelForConversation({ type: PanelType.AllMedia })}
|
||||||
>
|
>
|
||||||
|
|
|
@ -230,7 +230,6 @@ export async function sendDeleteForEveryone(
|
||||||
abortSignal,
|
abortSignal,
|
||||||
contentHint,
|
contentHint,
|
||||||
groupSendOptions: {
|
groupSendOptions: {
|
||||||
groupV1: conversation.getGroupV1Info(recipients),
|
|
||||||
groupV2: groupV2Info,
|
groupV2: groupV2Info,
|
||||||
deletedForEveryoneTimestamp: targetTimestamp,
|
deletedForEveryoneTimestamp: targetTimestamp,
|
||||||
timestamp,
|
timestamp,
|
||||||
|
|
|
@ -257,9 +257,6 @@ export async function sendNormalMessage(
|
||||||
contact,
|
contact,
|
||||||
deletedForEveryoneTimestamp,
|
deletedForEveryoneTimestamp,
|
||||||
expireTimer,
|
expireTimer,
|
||||||
groupV1: conversation.getGroupV1Info(
|
|
||||||
recipientIdentifiersWithoutMe
|
|
||||||
),
|
|
||||||
groupV2: groupV2Info,
|
groupV2: groupV2Info,
|
||||||
messageText: body,
|
messageText: body,
|
||||||
preview,
|
preview,
|
||||||
|
|
|
@ -152,7 +152,6 @@ export async function sendProfileKey(
|
||||||
contentHint,
|
contentHint,
|
||||||
groupSendOptions: {
|
groupSendOptions: {
|
||||||
flags: Proto.DataMessage.Flags.PROFILE_KEY_UPDATE,
|
flags: Proto.DataMessage.Flags.PROFILE_KEY_UPDATE,
|
||||||
groupV1: conversation.getGroupV1Info(),
|
|
||||||
groupV2: groupV2Info,
|
groupV2: groupV2Info,
|
||||||
profileKey,
|
profileKey,
|
||||||
timestamp,
|
timestamp,
|
||||||
|
|
|
@ -255,9 +255,6 @@ export async function sendReaction(
|
||||||
abortSignal,
|
abortSignal,
|
||||||
contentHint: ContentHint.RESENDABLE,
|
contentHint: ContentHint.RESENDABLE,
|
||||||
groupSendOptions: {
|
groupSendOptions: {
|
||||||
groupV1: conversation.getGroupV1Info(
|
|
||||||
recipientIdentifiersWithoutMe
|
|
||||||
),
|
|
||||||
groupV2: groupV2Info,
|
groupV2: groupV2Info,
|
||||||
reaction: reactionForSend,
|
reaction: reactionForSend,
|
||||||
timestamp: pendingReaction.timestamp,
|
timestamp: pendingReaction.timestamp,
|
||||||
|
|
|
@ -11,7 +11,6 @@ import * as Errors from '../types/errors';
|
||||||
export type MessageRequestAttributesType = {
|
export type MessageRequestAttributesType = {
|
||||||
threadE164?: string;
|
threadE164?: string;
|
||||||
threadUuid?: string;
|
threadUuid?: string;
|
||||||
groupId?: string;
|
|
||||||
groupV2Id?: string;
|
groupV2Id?: string;
|
||||||
type: number;
|
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
|
// V2 group
|
||||||
if (conversation.get('groupId')) {
|
if (conversation.get('groupId')) {
|
||||||
const syncByGroupId = this.findWhere({
|
const syncByGroupId = this.findWhere({
|
||||||
|
@ -91,7 +76,6 @@ export class MessageRequests extends Collection<MessageRequestModel> {
|
||||||
try {
|
try {
|
||||||
const threadE164 = sync.get('threadE164');
|
const threadE164 = sync.get('threadE164');
|
||||||
const threadUuid = sync.get('threadUuid');
|
const threadUuid = sync.get('threadUuid');
|
||||||
const groupId = sync.get('groupId');
|
|
||||||
const groupV2Id = sync.get('groupV2Id');
|
const groupV2Id = sync.get('groupV2Id');
|
||||||
|
|
||||||
let conversation;
|
let conversation;
|
||||||
|
@ -100,9 +84,6 @@ export class MessageRequests extends Collection<MessageRequestModel> {
|
||||||
if (groupV2Id) {
|
if (groupV2Id) {
|
||||||
conversation = window.ConversationController.get(groupV2Id);
|
conversation = window.ConversationController.get(groupV2Id);
|
||||||
}
|
}
|
||||||
if (!conversation && groupId) {
|
|
||||||
conversation = window.ConversationController.get(groupId);
|
|
||||||
}
|
|
||||||
if (!conversation && (threadE164 || threadUuid)) {
|
if (!conversation && (threadE164 || threadUuid)) {
|
||||||
conversation = window.ConversationController.lookupOrCreate({
|
conversation = window.ConversationController.lookupOrCreate({
|
||||||
e164: threadE164,
|
e164: threadE164,
|
||||||
|
@ -113,7 +94,7 @@ export class MessageRequests extends Collection<MessageRequestModel> {
|
||||||
|
|
||||||
if (!conversation) {
|
if (!conversation) {
|
||||||
log.warn(
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,6 @@ import * as Stickers from '../types/Stickers';
|
||||||
import { StorySendMode } from '../types/Stories';
|
import { StorySendMode } from '../types/Stories';
|
||||||
import type {
|
import type {
|
||||||
ContactWithHydratedAvatar,
|
ContactWithHydratedAvatar,
|
||||||
GroupV1InfoType,
|
|
||||||
GroupV2InfoType,
|
GroupV2InfoType,
|
||||||
} from '../textsecure/SendMessage';
|
} from '../textsecure/SendMessage';
|
||||||
import createTaskWithTimeout from '../textsecure/TaskWithTimeout';
|
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 {
|
getGroupIdBuffer(): Uint8Array | undefined {
|
||||||
const groupIdString = this.get('groupId');
|
const groupIdString = this.get('groupId');
|
||||||
|
|
||||||
|
@ -2457,9 +2438,7 @@ export class ConversationModel extends window.Backbone
|
||||||
this.disableProfileSharing({ viaStorageServiceSync });
|
this.disableProfileSharing({ viaStorageServiceSync });
|
||||||
|
|
||||||
if (isLocalAction) {
|
if (isLocalAction) {
|
||||||
if (isGroupV1(this.attributes)) {
|
if (isGroupV2(this.attributes)) {
|
||||||
await this.leaveGroup();
|
|
||||||
} else if (isGroupV2(this.attributes)) {
|
|
||||||
await this.leaveGroupV2();
|
await this.leaveGroupV2();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2477,9 +2456,7 @@ export class ConversationModel extends window.Backbone
|
||||||
'deleted from message request'
|
'deleted from message request'
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isGroupV1(this.attributes)) {
|
if (isGroupV2(this.attributes)) {
|
||||||
await this.leaveGroup();
|
|
||||||
} else if (isGroupV2(this.attributes)) {
|
|
||||||
await this.leaveGroupV2();
|
await this.leaveGroupV2();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2499,9 +2476,7 @@ export class ConversationModel extends window.Backbone
|
||||||
'blocked and deleted from message request'
|
'blocked and deleted from message request'
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isGroupV1(this.attributes)) {
|
if (isGroupV2(this.attributes)) {
|
||||||
await this.leaveGroup();
|
|
||||||
} else if (isGroupV2(this.attributes)) {
|
|
||||||
await this.leaveGroupV2();
|
await this.leaveGroupV2();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4875,59 +4850,6 @@ export class ConversationModel extends window.Backbone
|
||||||
return !this.get('left');
|
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(
|
async markRead(
|
||||||
newestUnreadAt: number,
|
newestUnreadAt: number,
|
||||||
options: {
|
options: {
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import {
|
import {
|
||||||
difference,
|
|
||||||
isEmpty,
|
isEmpty,
|
||||||
isEqual,
|
isEqual,
|
||||||
isNumber,
|
isNumber,
|
||||||
|
@ -14,11 +13,9 @@ import {
|
||||||
partition,
|
partition,
|
||||||
pick,
|
pick,
|
||||||
union,
|
union,
|
||||||
without,
|
|
||||||
} from 'lodash';
|
} from 'lodash';
|
||||||
import type {
|
import type {
|
||||||
CustomError,
|
CustomError,
|
||||||
GroupV1Update,
|
|
||||||
MessageAttributesType,
|
MessageAttributesType,
|
||||||
MessageReactionType,
|
MessageReactionType,
|
||||||
QuotedMessageType,
|
QuotedMessageType,
|
||||||
|
@ -85,7 +82,6 @@ import {
|
||||||
isDirectConversation,
|
isDirectConversation,
|
||||||
isGroup,
|
isGroup,
|
||||||
isGroupV1,
|
isGroupV1,
|
||||||
isGroupV2,
|
|
||||||
isMe,
|
isMe,
|
||||||
} from '../util/whatTypeOfConversation';
|
} from '../util/whatTypeOfConversation';
|
||||||
import { handleMessageSend } from '../util/handleMessageSend';
|
import { handleMessageSend } from '../util/handleMessageSend';
|
||||||
|
@ -145,11 +141,9 @@ import { notificationService } from '../services/notifications';
|
||||||
import type { LinkPreviewType } from '../types/message/LinkPreviews';
|
import type { LinkPreviewType } from '../types/message/LinkPreviews';
|
||||||
import * as log from '../logging/log';
|
import * as log from '../logging/log';
|
||||||
import * as Bytes from '../Bytes';
|
import * as Bytes from '../Bytes';
|
||||||
import { computeHash } from '../Crypto';
|
|
||||||
import { cleanupMessage, deleteMessageData } from '../util/cleanup';
|
import { cleanupMessage, deleteMessageData } from '../util/cleanup';
|
||||||
import {
|
import {
|
||||||
getContact,
|
getContact,
|
||||||
getContactId,
|
|
||||||
getSource,
|
getSource,
|
||||||
getSourceUuid,
|
getSourceUuid,
|
||||||
isCustomError,
|
isCustomError,
|
||||||
|
@ -173,7 +167,6 @@ import { SeenStatus } from '../MessageSeenStatus';
|
||||||
import { isNewReactionReplacingPrevious } from '../reactions/util';
|
import { isNewReactionReplacingPrevious } from '../reactions/util';
|
||||||
import { parseBoostBadgeListFromServer } from '../badges/parseBadgesFromServer';
|
import { parseBoostBadgeListFromServer } from '../badges/parseBadgesFromServer';
|
||||||
import { GiftBadgeStates } from '../components/conversation/Message';
|
import { GiftBadgeStates } from '../components/conversation/Message';
|
||||||
import { downloadAttachment } from '../util/downloadAttachment';
|
|
||||||
import type { StickerWithHydratedData } from '../types/Stickers';
|
import type { StickerWithHydratedData } from '../types/Stickers';
|
||||||
import { getStringForConversationMerge } from '../util/getStringForConversationMerge';
|
import { getStringForConversationMerge } from '../util/getStringForConversationMerge';
|
||||||
import {
|
import {
|
||||||
|
@ -2139,7 +2132,6 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
const sourceUuid = message.get('sourceUuid');
|
const sourceUuid = message.get('sourceUuid');
|
||||||
const type = message.get('type');
|
const type = message.get('type');
|
||||||
const conversationId = message.get('conversationId');
|
const conversationId = message.get('conversationId');
|
||||||
const GROUP_TYPES = Proto.GroupContext.Type;
|
|
||||||
|
|
||||||
const fromContact = getContact(this.attributes);
|
const fromContact = getContact(this.attributes);
|
||||||
if (fromContact) {
|
if (fromContact) {
|
||||||
|
@ -2352,9 +2344,6 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
reason: 'handleDataMessage',
|
reason: 'handleDataMessage',
|
||||||
})!;
|
})!;
|
||||||
const hasGroupV2Prop = Boolean(initialMessage.groupV2);
|
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.
|
// Drop if from blocked user. Only GroupV2 messages should need to be dropped here.
|
||||||
const isBlocked =
|
const isBlocked =
|
||||||
|
@ -2397,7 +2386,6 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
type === 'incoming' &&
|
type === 'incoming' &&
|
||||||
!isDirectConversation(conversation.attributes) &&
|
!isDirectConversation(conversation.attributes) &&
|
||||||
!hasGroupV2Prop &&
|
!hasGroupV2Prop &&
|
||||||
!isV1GroupUpdate &&
|
|
||||||
conversation.get('members') &&
|
conversation.get('members') &&
|
||||||
!areWeMember
|
!areWeMember
|
||||||
) {
|
) {
|
||||||
|
@ -2408,16 +2396,6 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
return;
|
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
|
// Drop incoming messages to announcement only groups where sender is not admin
|
||||||
if (
|
if (
|
||||||
conversation.get('announcementsOnly') &&
|
conversation.get('announcementsOnly') &&
|
||||||
|
@ -2615,160 +2593,10 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSupported) {
|
if (isSupported) {
|
||||||
let attributes = {
|
const attributes = {
|
||||||
...conversation.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
|
// Drop empty messages after. This needs to happen after the initial
|
||||||
// message.set call and after GroupV1 processing to make sure all possible
|
// message.set call and after GroupV1 processing to make sure all possible
|
||||||
// properties are set before we determine that a message is empty.
|
// properties are set before we determine that a message is empty.
|
||||||
|
|
|
@ -32,10 +32,6 @@ const PROCESSED_ATTACHMENT: ProcessedAttachment = {
|
||||||
size: 34,
|
size: 34,
|
||||||
};
|
};
|
||||||
|
|
||||||
const GROUP_ID = new Uint8Array([0x68, 0x65, 0x79]);
|
|
||||||
|
|
||||||
const DERIVED_GROUPV2_ID = '7qQUi8Wa6Jm3Rl+l63saATGeciEqokbHpP+lV3F5t9o=';
|
|
||||||
|
|
||||||
describe('processDataMessage', () => {
|
describe('processDataMessage', () => {
|
||||||
const check = (message: Proto.IDataMessage) =>
|
const check = (message: Proto.IDataMessage) =>
|
||||||
processDataMessage(
|
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', () => {
|
it('should process groupv2 context', () => {
|
||||||
const out = check({
|
const out = check({
|
||||||
groupV2: {
|
groupV2: {
|
||||||
|
@ -312,15 +255,10 @@ describe('processDataMessage', () => {
|
||||||
const out = check({
|
const out = check({
|
||||||
flags: FLAGS.END_SESSION,
|
flags: FLAGS.END_SESSION,
|
||||||
body: 'should be deleted',
|
body: 'should be deleted',
|
||||||
group: {
|
|
||||||
id: GROUP_ID,
|
|
||||||
type: Proto.GroupContext.Type.DELIVER,
|
|
||||||
},
|
|
||||||
attachments: [UNPROCESSED_ATTACHMENT],
|
attachments: [UNPROCESSED_ATTACHMENT],
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.isUndefined(out.body);
|
assert.isUndefined(out.body);
|
||||||
assert.isUndefined(out.group);
|
|
||||||
assert.deepStrictEqual(out.attachments, []);
|
assert.deepStrictEqual(out.attachments, []);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ import { parseIntOrThrow } from '../util/parseIntOrThrow';
|
||||||
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
|
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
|
||||||
import { Zone } from '../util/Zone';
|
import { Zone } from '../util/Zone';
|
||||||
import { DurationInSeconds } from '../util/durations';
|
import { DurationInSeconds } from '../util/durations';
|
||||||
import { deriveMasterKeyFromGroupV1, bytesToUuid } from '../Crypto';
|
import { bytesToUuid } from '../Crypto';
|
||||||
import type { DownloadedAttachmentType } from '../types/Attachment';
|
import type { DownloadedAttachmentType } from '../types/Attachment';
|
||||||
import { Address } from '../types/Address';
|
import { Address } from '../types/Address';
|
||||||
import { QualifiedAddress } from '../types/QualifiedAddress';
|
import { QualifiedAddress } from '../types/QualifiedAddress';
|
||||||
|
@ -127,7 +127,6 @@ import { inspectUnknownFieldTags } from '../util/inspectProtobufs';
|
||||||
import { incrementMessageCounter } from '../util/incrementMessageCounter';
|
import { incrementMessageCounter } from '../util/incrementMessageCounter';
|
||||||
import { filterAndClean } from '../types/BodyRange';
|
import { filterAndClean } from '../types/BodyRange';
|
||||||
|
|
||||||
const GROUPV1_ID_LENGTH = 16;
|
|
||||||
const GROUPV2_ID_LENGTH = 32;
|
const GROUPV2_ID_LENGTH = 32;
|
||||||
const RETRY_TIMEOUT = 2 * 60 * 1000;
|
const RETRY_TIMEOUT = 2 * 60 * 1000;
|
||||||
|
|
||||||
|
@ -2007,19 +2006,8 @@ export default class MessageReceiver
|
||||||
const message = this.processDecrypted(envelope, msg);
|
const message = this.processDecrypted(envelope, msg);
|
||||||
const groupId = this.getProcessedGroupId(message);
|
const groupId = this.getProcessedGroupId(message);
|
||||||
const isBlocked = groupId ? this.isGroupBlocked(groupId) : false;
|
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(
|
log.warn(
|
||||||
`Message ${getEnvelopeId(envelope)} ignored; destined for blocked group`
|
`Message ${getEnvelopeId(envelope)} ignored; destined for blocked group`
|
||||||
);
|
);
|
||||||
|
@ -2282,19 +2270,8 @@ export default class MessageReceiver
|
||||||
const message = this.processDecrypted(envelope, msg.dataMessage);
|
const message = this.processDecrypted(envelope, msg.dataMessage);
|
||||||
const groupId = this.getProcessedGroupId(message);
|
const groupId = this.getProcessedGroupId(message);
|
||||||
const isBlocked = groupId ? this.isGroupBlocked(groupId) : false;
|
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(
|
log.warn(
|
||||||
`Message ${getEnvelopeId(envelope)} ignored; destined for blocked group`
|
`Message ${getEnvelopeId(envelope)} ignored; destined for blocked group`
|
||||||
);
|
);
|
||||||
|
@ -2354,8 +2331,6 @@ export default class MessageReceiver
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.checkGroupV1Data(msg);
|
|
||||||
|
|
||||||
if (msg.flags && msg.flags & Proto.DataMessage.Flags.END_SESSION) {
|
if (msg.flags && msg.flags & Proto.DataMessage.Flags.END_SESSION) {
|
||||||
p = this.handleEndSession(envelope, new UUID(destination));
|
p = this.handleEndSession(envelope, new UUID(destination));
|
||||||
}
|
}
|
||||||
|
@ -2393,8 +2368,6 @@ export default class MessageReceiver
|
||||||
msg.flags & Proto.DataMessage.Flags.EXPIRATION_TIMER_UPDATE
|
msg.flags & Proto.DataMessage.Flags.EXPIRATION_TIMER_UPDATE
|
||||||
) {
|
) {
|
||||||
type = 'expirationTimerUpdate';
|
type = 'expirationTimerUpdate';
|
||||||
} else if (msg.group) {
|
|
||||||
type = 'legacyGroupChange';
|
|
||||||
}
|
}
|
||||||
// Note: other data messages without any of these attributes will fall into the
|
// Note: other data messages without any of these attributes will fall into the
|
||||||
// 'message' bucket - like stickers, gift badges, etc.
|
// 'message' bucket - like stickers, gift badges, etc.
|
||||||
|
@ -2404,19 +2377,8 @@ export default class MessageReceiver
|
||||||
const message = this.processDecrypted(envelope, msg);
|
const message = this.processDecrypted(envelope, msg);
|
||||||
const groupId = this.getProcessedGroupId(message);
|
const groupId = this.getProcessedGroupId(message);
|
||||||
const isBlocked = groupId ? this.isGroupBlocked(groupId) : false;
|
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(
|
log.warn(
|
||||||
`Message ${getEnvelopeId(envelope)} ignored; destined for blocked group`
|
`Message ${getEnvelopeId(envelope)} ignored; destined for blocked group`
|
||||||
);
|
);
|
||||||
|
@ -2780,18 +2742,12 @@ export default class MessageReceiver
|
||||||
|
|
||||||
const { groupId, timestamp, action } = typingMessage;
|
const { groupId, timestamp, action } = typingMessage;
|
||||||
|
|
||||||
let groupIdString: string | undefined;
|
|
||||||
let groupV2IdString: string | undefined;
|
let groupV2IdString: string | undefined;
|
||||||
if (groupId && groupId.byteLength > 0) {
|
if (groupId && groupId.byteLength === GROUPV2_ID_LENGTH) {
|
||||||
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);
|
groupV2IdString = Bytes.toBase64(groupId);
|
||||||
} else {
|
} else {
|
||||||
log.error('handleTypingMessage: Received invalid groupId value');
|
log.error('handleTypingMessage: Received invalid groupId value');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
this.dispatchEvent(
|
this.dispatchEvent(
|
||||||
new TypingEvent({
|
new TypingEvent({
|
||||||
|
@ -2799,13 +2755,11 @@ export default class MessageReceiver
|
||||||
senderUuid: envelope.sourceUuid,
|
senderUuid: envelope.sourceUuid,
|
||||||
senderDevice: envelope.sourceDevice,
|
senderDevice: envelope.sourceDevice,
|
||||||
typing: {
|
typing: {
|
||||||
|
groupV2Id: groupV2IdString,
|
||||||
typingMessage,
|
typingMessage,
|
||||||
timestamp: timestamp?.toNumber() ?? Date.now(),
|
timestamp: timestamp?.toNumber() ?? Date.now(),
|
||||||
started: action === Proto.TypingMessage.Action.STARTED,
|
started: action === Proto.TypingMessage.Action.STARTED,
|
||||||
stopped: action === Proto.TypingMessage.Action.STOPPED,
|
stopped: action === Proto.TypingMessage.Action.STOPPED,
|
||||||
|
|
||||||
groupId: groupIdString,
|
|
||||||
groupV2Id: groupV2IdString,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -2823,22 +2777,7 @@ export default class MessageReceiver
|
||||||
message: Proto.IDataMessage,
|
message: Proto.IDataMessage,
|
||||||
envelope: ProcessedEnvelope
|
envelope: ProcessedEnvelope
|
||||||
): boolean {
|
): boolean {
|
||||||
const { group, groupV2 } = message;
|
const { 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (groupV2) {
|
if (groupV2) {
|
||||||
const { masterKey } = groupV2;
|
const { masterKey } = groupV2;
|
||||||
|
@ -2857,46 +2796,12 @@ export default class MessageReceiver
|
||||||
return false;
|
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(
|
private getProcessedGroupId(
|
||||||
message: ProcessedDataMessage
|
message: ProcessedDataMessage
|
||||||
): string | undefined {
|
): string | undefined {
|
||||||
if (message.groupV2) {
|
if (message.groupV2) {
|
||||||
return message.groupV2.id;
|
return message.groupV2.id;
|
||||||
}
|
}
|
||||||
if (message.group && message.group.id) {
|
|
||||||
return message.group.id;
|
|
||||||
}
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2906,9 +2811,6 @@ export default class MessageReceiver
|
||||||
const { id } = deriveGroupFields(message.groupV2.masterKey);
|
const { id } = deriveGroupFields(message.groupV2.masterKey);
|
||||||
return Bytes.toBase64(id);
|
return Bytes.toBase64(id);
|
||||||
}
|
}
|
||||||
if (message.group && message.group.id) {
|
|
||||||
return Bytes.toBinary(message.group.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
@ -2917,10 +2819,6 @@ export default class MessageReceiver
|
||||||
if (sentMessage.message && sentMessage.message.groupV2) {
|
if (sentMessage.message && sentMessage.message.groupV2) {
|
||||||
return `groupv2(${this.getGroupId(sentMessage.message)})`;
|
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;
|
return sentMessage.destination || sentMessage.destinationUuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2998,8 +2896,6 @@ export default class MessageReceiver
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.checkGroupV1Data(sentMessage.message);
|
|
||||||
|
|
||||||
strictAssert(sentMessage.timestamp, 'sent message without timestamp');
|
strictAssert(sentMessage.timestamp, 'sent message without timestamp');
|
||||||
|
|
||||||
log.info(
|
log.info(
|
||||||
|
@ -3192,20 +3088,14 @@ export default class MessageReceiver
|
||||||
|
|
||||||
const { groupId } = sync;
|
const { groupId } = sync;
|
||||||
|
|
||||||
let groupIdString: string | undefined;
|
|
||||||
let groupV2IdString: string | undefined;
|
let groupV2IdString: string | undefined;
|
||||||
if (groupId && groupId.byteLength > 0) {
|
if (groupId && groupId.byteLength === GROUPV2_ID_LENGTH) {
|
||||||
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);
|
groupV2IdString = Bytes.toBase64(groupId);
|
||||||
} else {
|
} else {
|
||||||
this.removeFromCache(envelope);
|
this.removeFromCache(envelope);
|
||||||
log.error('Received message request with invalid groupId');
|
log.error('Received message request with invalid groupId');
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const ev = new MessageRequestResponseEvent(
|
const ev = new MessageRequestResponseEvent(
|
||||||
{
|
{
|
||||||
|
@ -3217,7 +3107,6 @@ export default class MessageReceiver
|
||||||
)
|
)
|
||||||
: undefined,
|
: undefined,
|
||||||
messageRequestResponseType: sync.type,
|
messageRequestResponseType: sync.type,
|
||||||
groupId: groupIdString,
|
|
||||||
groupV2Id: groupV2IdString,
|
groupV2Id: groupV2IdString,
|
||||||
},
|
},
|
||||||
this.removeFromCache.bind(this, envelope)
|
this.removeFromCache.bind(this, envelope)
|
||||||
|
@ -3583,14 +3472,10 @@ export default class MessageReceiver
|
||||||
|
|
||||||
if (blocked.groupIds) {
|
if (blocked.groupIds) {
|
||||||
const previous = this.storage.get('blocked-groups', []);
|
const previous = this.storage.get('blocked-groups', []);
|
||||||
const groupV1Ids: Array<string> = [];
|
|
||||||
const groupIds: Array<string> = [];
|
const groupIds: Array<string> = [];
|
||||||
|
|
||||||
blocked.groupIds.forEach(groupId => {
|
blocked.groupIds.forEach(groupId => {
|
||||||
if (groupId.byteLength === GROUPV1_ID_LENGTH) {
|
if (groupId.byteLength === GROUPV2_ID_LENGTH) {
|
||||||
groupV1Ids.push(Bytes.toBinary(groupId));
|
|
||||||
groupIds.push(this.deriveGroupV2FromV1(groupId));
|
|
||||||
} else if (groupId.byteLength === GROUPV2_ID_LENGTH) {
|
|
||||||
groupIds.push(Bytes.toBase64(groupId));
|
groupIds.push(Bytes.toBase64(groupId));
|
||||||
} else {
|
} else {
|
||||||
log.error('handleBlocked: Received invalid groupId value');
|
log.error('handleBlocked: Received invalid groupId value');
|
||||||
|
@ -3598,18 +3483,15 @@ export default class MessageReceiver
|
||||||
});
|
});
|
||||||
log.info(
|
log.info(
|
||||||
'handleBlocked: Blocking these groups - v2:',
|
'handleBlocked: Blocking these groups - v2:',
|
||||||
groupIds.map(groupId => `groupv2(${groupId})`),
|
groupIds.map(groupId => `groupv2(${groupId})`)
|
||||||
'v1:',
|
|
||||||
groupV1Ids.map(groupId => `group(${groupId})`)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const ids = [...groupIds, ...groupV1Ids];
|
await this.storage.put('blocked-groups', groupIds);
|
||||||
await this.storage.put('blocked-groups', ids);
|
|
||||||
|
|
||||||
if (!areArraysMatchingSets(previous, ids)) {
|
if (!areArraysMatchingSets(previous, groupIds)) {
|
||||||
changed = true;
|
changed = true;
|
||||||
allIdentifiers.push(...previous);
|
allIdentifiers.push(...previous);
|
||||||
allIdentifiers.push(...ids);
|
allIdentifiers.push(...groupIds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -103,10 +103,6 @@ export type GroupV2InfoType = {
|
||||||
revision: number;
|
revision: number;
|
||||||
members: ReadonlyArray<string>;
|
members: ReadonlyArray<string>;
|
||||||
};
|
};
|
||||||
export type GroupV1InfoType = {
|
|
||||||
id: string;
|
|
||||||
members: ReadonlyArray<string>;
|
|
||||||
};
|
|
||||||
|
|
||||||
type GroupCallUpdateType = {
|
type GroupCallUpdateType = {
|
||||||
eraId: string;
|
eraId: string;
|
||||||
|
@ -207,7 +203,6 @@ export type GroupSendOptionsType = {
|
||||||
expireTimer?: DurationInSeconds;
|
expireTimer?: DurationInSeconds;
|
||||||
flags?: number;
|
flags?: number;
|
||||||
groupCallUpdate?: GroupCallUpdateType;
|
groupCallUpdate?: GroupCallUpdateType;
|
||||||
groupV1?: GroupV1InfoType;
|
|
||||||
groupV2?: GroupV2InfoType;
|
groupV2?: GroupV2InfoType;
|
||||||
messageText?: string;
|
messageText?: string;
|
||||||
preview?: ReadonlyArray<LinkPreviewType>;
|
preview?: ReadonlyArray<LinkPreviewType>;
|
||||||
|
@ -381,10 +376,6 @@ class Message {
|
||||||
proto.groupV2.masterKey = this.groupV2.masterKey;
|
proto.groupV2.masterKey = this.groupV2.masterKey;
|
||||||
proto.groupV2.revision = this.groupV2.revision;
|
proto.groupV2.revision = this.groupV2.revision;
|
||||||
proto.groupV2.groupChange = this.groupV2.groupChange || null;
|
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) {
|
if (this.sticker) {
|
||||||
proto.sticker = new Proto.DataMessage.Sticker();
|
proto.sticker = new Proto.DataMessage.Sticker();
|
||||||
|
@ -1106,7 +1097,6 @@ export default class MessageSender {
|
||||||
expireTimer,
|
expireTimer,
|
||||||
flags,
|
flags,
|
||||||
groupCallUpdate,
|
groupCallUpdate,
|
||||||
groupV1,
|
|
||||||
groupV2,
|
groupV2,
|
||||||
messageText,
|
messageText,
|
||||||
preview,
|
preview,
|
||||||
|
@ -1118,16 +1108,16 @@ export default class MessageSender {
|
||||||
timestamp,
|
timestamp,
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
if (!groupV1 && !groupV2) {
|
if (!groupV2) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'getAttrsFromGroupOptions: Neither group1 nor groupv2 information provided!'
|
'getAttrsFromGroupOptions: No groupv2 information provided!'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const myE164 = window.textsecure.storage.user.getNumber();
|
const myE164 = window.textsecure.storage.user.getNumber();
|
||||||
const myUuid = window.textsecure.storage.user.getUuid()?.toString();
|
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.
|
// We should always have a UUID but have this check just in case we don't.
|
||||||
let isNotMe: (recipient: string) => boolean;
|
let isNotMe: (recipient: string) => boolean;
|
||||||
|
@ -1158,12 +1148,6 @@ export default class MessageSender {
|
||||||
flags,
|
flags,
|
||||||
groupCallUpdate,
|
groupCallUpdate,
|
||||||
groupV2,
|
groupV2,
|
||||||
group: groupV1
|
|
||||||
? {
|
|
||||||
id: groupV1.id,
|
|
||||||
type: Proto.GroupContext.Type.DELIVER,
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
preview,
|
preview,
|
||||||
profileKey,
|
profileKey,
|
||||||
quote,
|
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
|
// Simple pass-throughs
|
||||||
|
|
||||||
// Note: instead of updating these functions, or adding new ones, remove these and go
|
// 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'>;
|
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 = {
|
export type ProcessedGroupV2Context = {
|
||||||
masterKey: string;
|
masterKey: string;
|
||||||
revision?: number;
|
revision?: number;
|
||||||
|
@ -208,7 +197,6 @@ export type ProcessedGiftBadge = {
|
||||||
export type ProcessedDataMessage = {
|
export type ProcessedDataMessage = {
|
||||||
body?: string;
|
body?: string;
|
||||||
attachments: ReadonlyArray<ProcessedAttachment>;
|
attachments: ReadonlyArray<ProcessedAttachment>;
|
||||||
group?: ProcessedGroupContext;
|
|
||||||
groupV2?: ProcessedGroupV2Context;
|
groupV2?: ProcessedGroupV2Context;
|
||||||
flags: number;
|
flags: number;
|
||||||
expireTimer: DurationInSeconds;
|
expireTimer: DurationInSeconds;
|
||||||
|
|
|
@ -10,12 +10,10 @@ import { dropNull, shallowDropNull } from '../util/dropNull';
|
||||||
import { SignalService as Proto } from '../protobuf';
|
import { SignalService as Proto } from '../protobuf';
|
||||||
import { deriveGroupFields } from '../groups';
|
import { deriveGroupFields } from '../groups';
|
||||||
import * as Bytes from '../Bytes';
|
import * as Bytes from '../Bytes';
|
||||||
import { deriveMasterKeyFromGroupV1 } from '../Crypto';
|
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ProcessedAttachment,
|
ProcessedAttachment,
|
||||||
ProcessedDataMessage,
|
ProcessedDataMessage,
|
||||||
ProcessedGroupContext,
|
|
||||||
ProcessedGroupV2Context,
|
ProcessedGroupV2Context,
|
||||||
ProcessedQuote,
|
ProcessedQuote,
|
||||||
ProcessedContact,
|
ProcessedContact,
|
||||||
|
@ -25,7 +23,6 @@ import type {
|
||||||
ProcessedDelete,
|
ProcessedDelete,
|
||||||
ProcessedGiftBadge,
|
ProcessedGiftBadge,
|
||||||
} from './Types.d';
|
} from './Types.d';
|
||||||
import { WarnOnlyError } from './Errors';
|
|
||||||
import { GiftBadgeStates } from '../components/conversation/Message';
|
import { GiftBadgeStates } from '../components/conversation/Message';
|
||||||
import { APPLICATION_OCTET_STREAM, stringToMIMEType } from '../types/MIME';
|
import { APPLICATION_OCTET_STREAM, stringToMIMEType } from '../types/MIME';
|
||||||
import { SECOND, DurationInSeconds } from '../util/durations';
|
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(
|
export function processGroupV2Context(
|
||||||
groupV2?: Proto.IGroupContextV2 | null
|
groupV2?: Proto.IGroupContextV2 | null
|
||||||
): ProcessedGroupV2Context | undefined {
|
): ProcessedGroupV2Context | undefined {
|
||||||
|
@ -331,7 +295,6 @@ export function processDataMessage(
|
||||||
attachments: (message.attachments ?? []).map(
|
attachments: (message.attachments ?? []).map(
|
||||||
(attachment: Proto.IAttachmentPointer) => processAttachment(attachment)
|
(attachment: Proto.IAttachmentPointer) => processAttachment(attachment)
|
||||||
),
|
),
|
||||||
group: processGroupContext(message.group),
|
|
||||||
groupV2: processGroupV2Context(message.groupV2),
|
groupV2: processGroupV2Context(message.groupV2),
|
||||||
flags: message.flags ?? 0,
|
flags: message.flags ?? 0,
|
||||||
expireTimer: DurationInSeconds.fromSeconds(message.expireTimer ?? 0),
|
expireTimer: DurationInSeconds.fromSeconds(message.expireTimer ?? 0),
|
||||||
|
@ -375,7 +338,6 @@ export function processDataMessage(
|
||||||
if (isEndSession) {
|
if (isEndSession) {
|
||||||
result.body = undefined;
|
result.body = undefined;
|
||||||
result.attachments = [];
|
result.attachments = [];
|
||||||
result.group = undefined;
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -389,27 +351,6 @@ export function processDataMessage(
|
||||||
throw new Error(`Unknown flags in message: ${result.flags}`);
|
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;
|
const attachmentCount = result.attachments.length;
|
||||||
if (attachmentCount > ATTACHMENT_MAX) {
|
if (attachmentCount > ATTACHMENT_MAX) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|
|
@ -859,9 +859,6 @@ function getRecipients(options: GroupSendOptionsType): ReadonlyArray<string> {
|
||||||
if (options.groupV2) {
|
if (options.groupV2) {
|
||||||
return options.groupV2.members;
|
return options.groupV2.members;
|
||||||
}
|
}
|
||||||
if (options.groupV1) {
|
|
||||||
return options.groupV1.members;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error('getRecipients: Unable to extract recipients!');
|
throw new Error('getRecipients: Unable to extract recipients!');
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue