Send stories to groups capability
This commit is contained in:
parent
62962e4950
commit
2f5dd73e58
5 changed files with 308 additions and 163 deletions
|
@ -10,6 +10,10 @@
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__debugger__container {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
&__debugger__button {
|
&__debugger__button {
|
||||||
color: $color-gray-25;
|
color: $color-gray-25;
|
||||||
display: block;
|
display: block;
|
||||||
|
|
|
@ -36,6 +36,7 @@ export type PropsType = {
|
||||||
onClose: () => unknown;
|
onClose: () => unknown;
|
||||||
onSend: (
|
onSend: (
|
||||||
listIds: Array<UUIDStringType>,
|
listIds: Array<UUIDStringType>,
|
||||||
|
conversationIds: Array<string>,
|
||||||
attachment: AttachmentType
|
attachment: AttachmentType
|
||||||
) => unknown;
|
) => unknown;
|
||||||
processAttachment: (
|
processAttachment: (
|
||||||
|
@ -104,7 +105,7 @@ export const StoryCreator = ({
|
||||||
me={me}
|
me={me}
|
||||||
onClose={() => setDraftAttachment(undefined)}
|
onClose={() => setDraftAttachment(undefined)}
|
||||||
onSend={listIds => {
|
onSend={listIds => {
|
||||||
onSend(listIds, draftAttachment);
|
onSend(listIds, [], draftAttachment);
|
||||||
setDraftAttachment(undefined);
|
setDraftAttachment(undefined);
|
||||||
onClose();
|
onClose();
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -29,7 +29,7 @@ import {
|
||||||
} from '../../util/getSendOptions';
|
} from '../../util/getSendOptions';
|
||||||
import { handleMessageSend } from '../../util/handleMessageSend';
|
import { handleMessageSend } from '../../util/handleMessageSend';
|
||||||
import { handleMultipleSendErrors } from './handleMultipleSendErrors';
|
import { handleMultipleSendErrors } from './handleMultipleSendErrors';
|
||||||
import { isMe } from '../../util/whatTypeOfConversation';
|
import { isGroupV2, isMe } from '../../util/whatTypeOfConversation';
|
||||||
import { isNotNil } from '../../util/isNotNil';
|
import { isNotNil } from '../../util/isNotNil';
|
||||||
import { isSent } from '../../messages/MessageSendState';
|
import { isSent } from '../../messages/MessageSendState';
|
||||||
import { ourProfileKeyService } from '../../services/ourProfileKey';
|
import { ourProfileKeyService } from '../../services/ourProfileKey';
|
||||||
|
@ -66,7 +66,7 @@ export async function sendStory(
|
||||||
const message = await getMessageById(messageId);
|
const message = await getMessageById(messageId);
|
||||||
if (!message) {
|
if (!message) {
|
||||||
log.info(
|
log.info(
|
||||||
`stories.sendStory: message ${messageId} was not found, maybe because it was deleted. Giving up on sending it`
|
`stories.sendStory(${messageId}): message was not found, maybe because it was deleted. Giving up on sending it`
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -76,7 +76,7 @@ export async function sendStory(
|
||||||
|
|
||||||
if (!attachment) {
|
if (!attachment) {
|
||||||
log.info(
|
log.info(
|
||||||
`stories.sendStory: message ${messageId} does not have any attachments to send. Giving up on sending it`
|
`stories.sendStory(${messageId}): message does not have any attachments to send. Giving up on sending it`
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -107,15 +107,16 @@ export async function sendStory(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const accSendStateByConversationId = new Map<string, SendState>();
|
|
||||||
const canReplyUuids = new Set<string>();
|
const canReplyUuids = new Set<string>();
|
||||||
const recipientsByUuid = new Map<string, Set<string>>();
|
const recipientsByUuid = new Map<string, Set<string>>();
|
||||||
|
const sentConversationIds = new Map<string, SendState>();
|
||||||
|
const sentUuids = new Set<string>();
|
||||||
|
|
||||||
// This function is used to keep track of all the recipients so once we're
|
// This function is used to keep track of all the recipients so once we're
|
||||||
// done with our send we can build up the storyMessageRecipients object for
|
// done with our send we can build up the storyMessageRecipients object for
|
||||||
// sending in the sync message.
|
// sending in the sync message.
|
||||||
function processStoryMessageRecipient(
|
function addDistributionListToUuidSent(
|
||||||
listId: string,
|
listId: string | undefined,
|
||||||
uuid: string,
|
uuid: string,
|
||||||
canReply?: boolean
|
canReply?: boolean
|
||||||
): void {
|
): void {
|
||||||
|
@ -125,46 +126,17 @@ export async function sendStory(
|
||||||
|
|
||||||
const distributionListIds = recipientsByUuid.get(uuid) || new Set<string>();
|
const distributionListIds = recipientsByUuid.get(uuid) || new Set<string>();
|
||||||
|
|
||||||
recipientsByUuid.set(uuid, new Set([...distributionListIds, listId]));
|
if (listId) {
|
||||||
|
recipientsByUuid.set(uuid, new Set([...distributionListIds, listId]));
|
||||||
|
} else {
|
||||||
|
recipientsByUuid.set(uuid, distributionListIds);
|
||||||
|
}
|
||||||
|
|
||||||
if (canReply) {
|
if (canReply) {
|
||||||
canReplyUuids.add(uuid);
|
canReplyUuids.add(uuid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since some contacts will be duplicated across lists but we won't be sending
|
|
||||||
// duplicate messages we need to ensure that sendStateByConversationId is kept
|
|
||||||
// in sync across all messages.
|
|
||||||
async function maybeUpdateMessageSendState(
|
|
||||||
message: MessageModel
|
|
||||||
): Promise<void> {
|
|
||||||
const oldSendStateByConversationId =
|
|
||||||
message.get('sendStateByConversationId') || {};
|
|
||||||
|
|
||||||
const newSendStateByConversationId = Object.keys(
|
|
||||||
oldSendStateByConversationId
|
|
||||||
).reduce((acc, conversationId) => {
|
|
||||||
const sendState = accSendStateByConversationId.get(conversationId);
|
|
||||||
if (sendState) {
|
|
||||||
return {
|
|
||||||
...acc,
|
|
||||||
[conversationId]: sendState,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, {} as SendStateByConversationId);
|
|
||||||
|
|
||||||
if (isEqual(oldSendStateByConversationId, newSendStateByConversationId)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
message.set('sendStateByConversationId', newSendStateByConversationId);
|
|
||||||
await window.Signal.Data.saveMessage(message.attributes, {
|
|
||||||
ourUuid: window.textsecure.storage.user.getCheckedUuid().toString(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let isSyncMessageUpdate = false;
|
let isSyncMessageUpdate = false;
|
||||||
|
|
||||||
// Send to all distribution lists
|
// Send to all distribution lists
|
||||||
|
@ -173,7 +145,7 @@ export async function sendStory(
|
||||||
const message = await getMessageById(messageId);
|
const message = await getMessageById(messageId);
|
||||||
if (!message) {
|
if (!message) {
|
||||||
log.info(
|
log.info(
|
||||||
`stories.sendStory: message ${messageId} was not found, maybe because it was deleted. Giving up on sending it`
|
`stories.sendStory(${messageId}): message was not found, maybe because it was deleted. Giving up on sending it`
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -188,29 +160,26 @@ export async function sendStory(
|
||||||
|
|
||||||
if (message.isErased() || message.get('deletedForEveryone')) {
|
if (message.isErased() || message.get('deletedForEveryone')) {
|
||||||
log.info(
|
log.info(
|
||||||
`stories.sendStory: message ${messageId} was erased. Giving up on sending it`
|
`stories.sendStory(${messageId}): message was erased. Giving up on sending it`
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const listId = message.get('storyDistributionListId');
|
const listId = message.get('storyDistributionListId');
|
||||||
|
const receiverId = isGroupV2(messageConversation.attributes)
|
||||||
|
? messageConversation.id
|
||||||
|
: listId;
|
||||||
|
|
||||||
if (!listId) {
|
if (!receiverId) {
|
||||||
log.info(
|
log.info(
|
||||||
`stories.sendStory: message ${messageId} does not have a storyDistributionListId. Giving up on sending it`
|
`stories.sendStory(${messageId}): did not get a valid recipient ID for message. Giving up on sending it`
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const distributionList =
|
const distributionList = isGroupV2(messageConversation.attributes)
|
||||||
await dataInterface.getStoryDistributionWithMembers(listId);
|
? undefined
|
||||||
|
: await dataInterface.getStoryDistributionWithMembers(receiverId);
|
||||||
if (!distributionList) {
|
|
||||||
log.info(
|
|
||||||
`stories.sendStory: Distribution list ${listId} was not found. Giving up on sending message ${messageId}`
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let messageSendErrors: Array<Error> = [];
|
let messageSendErrors: Array<Error> = [];
|
||||||
|
|
||||||
|
@ -230,7 +199,7 @@ export async function sendStory(
|
||||||
|
|
||||||
if (!shouldContinue) {
|
if (!shouldContinue) {
|
||||||
log.info(
|
log.info(
|
||||||
`stories.sendStory: message ${messageId} ran out of time. Giving up on sending it`
|
`stories.sendStory(${messageId}): ran out of time. Giving up on sending it`
|
||||||
);
|
);
|
||||||
await markMessageFailed(message, [
|
await markMessageFailed(message, [
|
||||||
new Error('Message send ran out of time'),
|
new Error('Message send ran out of time'),
|
||||||
|
@ -241,10 +210,10 @@ export async function sendStory(
|
||||||
let originalError: Error | undefined;
|
let originalError: Error | undefined;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
allRecipientIdentifiers,
|
allRecipientIds,
|
||||||
allowedReplyByUuid,
|
allowedReplyByUuid,
|
||||||
recipientIdentifiersWithoutMe,
|
pendingSendRecipientIds,
|
||||||
sentRecipientIdentifiers,
|
sentRecipientIds,
|
||||||
untrustedUuids,
|
untrustedUuids,
|
||||||
} = getMessageRecipients({
|
} = getMessageRecipients({
|
||||||
log,
|
log,
|
||||||
|
@ -260,39 +229,31 @@ export async function sendStory(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`stories.sendStory: Message ${messageId} sending blocked because ${untrustedUuids.length} conversation(s) were untrusted. Failing this attempt.`
|
`stories.sendStory(${messageId}): sending blocked because ${untrustedUuids.length} conversation(s) were untrusted. Failing this attempt.`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (!pendingSendRecipientIds.length) {
|
||||||
!allRecipientIdentifiers.length ||
|
allRecipientIds.forEach(uuid =>
|
||||||
!recipientIdentifiersWithoutMe.length
|
addDistributionListToUuidSent(
|
||||||
) {
|
|
||||||
log.info(
|
|
||||||
`stories.sendStory: trying to send message ${messageId} but it looks like it was already sent to everyone.`
|
|
||||||
);
|
|
||||||
sentRecipientIdentifiers.forEach(uuid =>
|
|
||||||
processStoryMessageRecipient(
|
|
||||||
listId,
|
listId,
|
||||||
uuid,
|
uuid,
|
||||||
allowedReplyByUuid.get(uuid)
|
allowedReplyByUuid.get(uuid)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
await maybeUpdateMessageSendState(message);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
|
|
||||||
const recipientsSet = new Set(recipientIdentifiersWithoutMe);
|
const recipientsSet = new Set(pendingSendRecipientIds);
|
||||||
|
|
||||||
const sendOptions = await getSendOptionsForRecipients(
|
const sendOptions = await getSendOptionsForRecipients(
|
||||||
recipientIdentifiersWithoutMe
|
pendingSendRecipientIds
|
||||||
);
|
);
|
||||||
|
|
||||||
log.info(
|
log.info(
|
||||||
'stories.sendStory: sending story to distribution list',
|
`stories.sendStory(${messageId}): sending story to ${receiverId}`
|
||||||
listId
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const storyMessage = new Proto.StoryMessage();
|
const storyMessage = new Proto.StoryMessage();
|
||||||
|
@ -300,7 +261,29 @@ export async function sendStory(
|
||||||
storyMessage.fileAttachment = originalStoryMessage.fileAttachment;
|
storyMessage.fileAttachment = originalStoryMessage.fileAttachment;
|
||||||
storyMessage.textAttachment = originalStoryMessage.textAttachment;
|
storyMessage.textAttachment = originalStoryMessage.textAttachment;
|
||||||
storyMessage.group = originalStoryMessage.group;
|
storyMessage.group = originalStoryMessage.group;
|
||||||
storyMessage.allowsReplies = Boolean(distributionList.allowsReplies);
|
storyMessage.allowsReplies =
|
||||||
|
isGroupV2(messageConversation.attributes) ||
|
||||||
|
Boolean(distributionList?.allowsReplies);
|
||||||
|
|
||||||
|
const sendTarget = distributionList
|
||||||
|
? {
|
||||||
|
getGroupId: () => undefined,
|
||||||
|
getMembers: () =>
|
||||||
|
pendingSendRecipientIds
|
||||||
|
.map(uuid => window.ConversationController.get(uuid))
|
||||||
|
.filter(isNotNil),
|
||||||
|
hasMember: (uuid: UUIDStringType) => recipientsSet.has(uuid),
|
||||||
|
idForLogging: () => `dl(${receiverId})`,
|
||||||
|
isGroupV2: () => true,
|
||||||
|
isValid: () => true,
|
||||||
|
getSenderKeyInfo: () => distributionList.senderKeyInfo,
|
||||||
|
saveSenderKeyInfo: async (senderKeyInfo: SenderKeyInfoType) =>
|
||||||
|
dataInterface.modifyStoryDistribution({
|
||||||
|
...distributionList,
|
||||||
|
senderKeyInfo,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
: conversation.toSenderKeyTarget();
|
||||||
|
|
||||||
const contentMessage = new Proto.Content();
|
const contentMessage = new Proto.Content();
|
||||||
contentMessage.storyMessage = storyMessage;
|
contentMessage.storyMessage = storyMessage;
|
||||||
|
@ -310,25 +293,9 @@ export async function sendStory(
|
||||||
contentMessage,
|
contentMessage,
|
||||||
isPartialSend: false,
|
isPartialSend: false,
|
||||||
messageId: undefined,
|
messageId: undefined,
|
||||||
recipients: recipientIdentifiersWithoutMe,
|
recipients: pendingSendRecipientIds,
|
||||||
sendOptions,
|
sendOptions,
|
||||||
sendTarget: {
|
sendTarget,
|
||||||
getGroupId: () => undefined,
|
|
||||||
getMembers: () =>
|
|
||||||
recipientIdentifiersWithoutMe
|
|
||||||
.map(uuid => window.ConversationController.get(uuid))
|
|
||||||
.filter(isNotNil),
|
|
||||||
hasMember: (uuid: UUIDStringType) => recipientsSet.has(uuid),
|
|
||||||
idForLogging: () => `dl(${listId})`,
|
|
||||||
isGroupV2: () => true,
|
|
||||||
isValid: () => true,
|
|
||||||
getSenderKeyInfo: () => distributionList.senderKeyInfo,
|
|
||||||
saveSenderKeyInfo: async (senderKeyInfo: SenderKeyInfoType) =>
|
|
||||||
dataInterface.modifyStoryDistribution({
|
|
||||||
...distributionList,
|
|
||||||
senderKeyInfo,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
sendType: 'story',
|
sendType: 'story',
|
||||||
timestamp,
|
timestamp,
|
||||||
urgent: false,
|
urgent: false,
|
||||||
|
@ -369,17 +336,31 @@ export async function sendStory(
|
||||||
message.get('sendStateByConversationId') || {};
|
message.get('sendStateByConversationId') || {};
|
||||||
Object.entries(sendStateByConversationId).forEach(
|
Object.entries(sendStateByConversationId).forEach(
|
||||||
([recipientConversationId, sendState]) => {
|
([recipientConversationId, sendState]) => {
|
||||||
if (accSendStateByConversationId.has(recipientConversationId)) {
|
if (!isSent(sendState.status)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
accSendStateByConversationId.set(
|
sentConversationIds.set(recipientConversationId, sendState);
|
||||||
recipientConversationId,
|
|
||||||
sendState
|
const recipient = window.ConversationController.get(
|
||||||
|
recipientConversationId
|
||||||
);
|
);
|
||||||
|
const uuid = recipient?.get('uuid');
|
||||||
|
if (!uuid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sentUuids.add(uuid);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
allRecipientIds.forEach(uuid => {
|
||||||
|
addDistributionListToUuidSent(
|
||||||
|
listId,
|
||||||
|
uuid,
|
||||||
|
allowedReplyByUuid.get(uuid)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const didFullySend =
|
const didFullySend =
|
||||||
!messageSendErrors.length || didSendToEveryone(message);
|
!messageSendErrors.length || didSendToEveryone(message);
|
||||||
if (!didFullySend) {
|
if (!didFullySend) {
|
||||||
|
@ -400,21 +381,59 @@ export async function sendStory(
|
||||||
toThrow: originalError || thrownError,
|
toThrow: originalError || thrownError,
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
recipientIdentifiersWithoutMe.forEach(uuid =>
|
isSyncMessageUpdate = sentRecipientIds.length > 0;
|
||||||
processStoryMessageRecipient(
|
|
||||||
listId,
|
|
||||||
uuid,
|
|
||||||
allowedReplyByUuid.get(uuid)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
// Greater than 1 because our own conversation will always count as "sent"
|
|
||||||
isSyncMessageUpdate = sentRecipientIdentifiers.length > 1;
|
|
||||||
await maybeUpdateMessageSendState(message);
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// Send the sync message
|
// Some contacts are duplicated across lists and we don't send duplicate
|
||||||
|
// messages but we still want to make sure that the sendStateByConversationId
|
||||||
|
// is kept in sync across all messages.
|
||||||
|
await Promise.all(
|
||||||
|
messageIds.map(async messageId => {
|
||||||
|
const message = await getMessageById(messageId);
|
||||||
|
if (!message) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldSendStateByConversationId =
|
||||||
|
message.get('sendStateByConversationId') || {};
|
||||||
|
|
||||||
|
const newSendStateByConversationId = Object.keys(
|
||||||
|
oldSendStateByConversationId
|
||||||
|
).reduce((acc, conversationId) => {
|
||||||
|
const sendState = sentConversationIds.get(conversationId);
|
||||||
|
if (sendState) {
|
||||||
|
return {
|
||||||
|
...acc,
|
||||||
|
[conversationId]: sendState,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {} as SendStateByConversationId);
|
||||||
|
|
||||||
|
if (isEqual(oldSendStateByConversationId, newSendStateByConversationId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
message.set('sendStateByConversationId', newSendStateByConversationId);
|
||||||
|
return window.Signal.Data.saveMessage(message.attributes, {
|
||||||
|
ourUuid: window.textsecure.storage.user.getCheckedUuid().toString(),
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Remove any unsent recipients
|
||||||
|
recipientsByUuid.forEach((_value, uuid) => {
|
||||||
|
if (sentUuids.has(uuid)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
recipientsByUuid.delete(uuid);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build up the sync message's storyMessageRecipients and send it
|
||||||
const storyMessageRecipients: Array<{
|
const storyMessageRecipients: Array<{
|
||||||
destinationUuid: string;
|
destinationUuid: string;
|
||||||
distributionListIds: Array<string>;
|
distributionListIds: Array<string>;
|
||||||
|
@ -452,24 +471,20 @@ function getMessageRecipients({
|
||||||
log: LoggerType;
|
log: LoggerType;
|
||||||
message: MessageModel;
|
message: MessageModel;
|
||||||
}>): {
|
}>): {
|
||||||
allRecipientIdentifiers: Array<string>;
|
allRecipientIds: Array<string>;
|
||||||
allowedReplyByUuid: Map<string, boolean>;
|
allowedReplyByUuid: Map<string, boolean>;
|
||||||
recipientIdentifiersWithoutMe: Array<string>;
|
pendingSendRecipientIds: Array<string>;
|
||||||
sentRecipientIdentifiers: Array<string>;
|
sentRecipientIds: Array<string>;
|
||||||
untrustedUuids: Array<string>;
|
untrustedUuids: Array<string>;
|
||||||
} {
|
} {
|
||||||
const allRecipientIdentifiers: Array<string> = [];
|
const allRecipientIds: Array<string> = [];
|
||||||
const recipientIdentifiersWithoutMe: Array<string> = [];
|
|
||||||
const untrustedUuids: Array<string> = [];
|
|
||||||
const sentRecipientIdentifiers: Array<string> = [];
|
|
||||||
const allowedReplyByUuid = new Map<string, boolean>();
|
const allowedReplyByUuid = new Map<string, boolean>();
|
||||||
|
const pendingSendRecipientIds: Array<string> = [];
|
||||||
|
const sentRecipientIds: Array<string> = [];
|
||||||
|
const untrustedUuids: Array<string> = [];
|
||||||
|
|
||||||
Object.entries(message.get('sendStateByConversationId') || {}).forEach(
|
Object.entries(message.get('sendStateByConversationId') || {}).forEach(
|
||||||
([recipientConversationId, sendState]) => {
|
([recipientConversationId, sendState]) => {
|
||||||
if (sendState.isAlreadyIncludedInAnotherDistributionList) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const recipient = window.ConversationController.get(
|
const recipient = window.ConversationController.get(
|
||||||
recipientConversationId
|
recipientConversationId
|
||||||
);
|
);
|
||||||
|
@ -478,6 +493,9 @@ function getMessageRecipients({
|
||||||
}
|
}
|
||||||
|
|
||||||
const isRecipientMe = isMe(recipient.attributes);
|
const isRecipientMe = isMe(recipient.attributes);
|
||||||
|
if (isRecipientMe) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (recipient.isUntrusted()) {
|
if (recipient.isUntrusted()) {
|
||||||
const uuid = recipient.get('uuid');
|
const uuid = recipient.get('uuid');
|
||||||
|
@ -494,33 +512,35 @@ function getMessageRecipients({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const recipientIdentifier = recipient.getSendTarget();
|
const recipientSendTarget = recipient.getSendTarget();
|
||||||
if (!recipientIdentifier) {
|
if (!recipientSendTarget) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
allowedReplyByUuid.set(
|
allowedReplyByUuid.set(
|
||||||
recipientIdentifier,
|
recipientSendTarget,
|
||||||
Boolean(sendState.isAllowedToReplyToStory)
|
Boolean(sendState.isAllowedToReplyToStory)
|
||||||
);
|
);
|
||||||
|
allRecipientIds.push(recipientSendTarget);
|
||||||
|
|
||||||
if (isSent(sendState.status)) {
|
if (sendState.isAlreadyIncludedInAnotherDistributionList) {
|
||||||
sentRecipientIdentifiers.push(recipientIdentifier);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
allRecipientIdentifiers.push(recipientIdentifier);
|
if (isSent(sendState.status)) {
|
||||||
if (!isRecipientMe) {
|
sentRecipientIds.push(recipientSendTarget);
|
||||||
recipientIdentifiersWithoutMe.push(recipientIdentifier);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pendingSendRecipientIds.push(recipientSendTarget);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
allRecipientIdentifiers,
|
allRecipientIds,
|
||||||
allowedReplyByUuid,
|
allowedReplyByUuid,
|
||||||
recipientIdentifiersWithoutMe,
|
pendingSendRecipientIds,
|
||||||
sentRecipientIdentifiers,
|
sentRecipientIds,
|
||||||
untrustedUuids,
|
untrustedUuids,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -539,7 +559,9 @@ async function markMessageFailed(
|
||||||
function didSendToEveryone(message: Readonly<MessageModel>): boolean {
|
function didSendToEveryone(message: Readonly<MessageModel>): boolean {
|
||||||
const sendStateByConversationId =
|
const sendStateByConversationId =
|
||||||
message.get('sendStateByConversationId') || {};
|
message.get('sendStateByConversationId') || {};
|
||||||
return Object.values(sendStateByConversationId).every(sendState =>
|
return Object.values(sendStateByConversationId).every(
|
||||||
isSent(sendState.status)
|
sendState =>
|
||||||
|
sendState.isAlreadyIncludedInAnotherDistributionList ||
|
||||||
|
isSent(sendState.status)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -559,10 +559,11 @@ function replyToStory(
|
||||||
|
|
||||||
function sendStoryMessage(
|
function sendStoryMessage(
|
||||||
listIds: Array<UUIDStringType>,
|
listIds: Array<UUIDStringType>,
|
||||||
|
conversationIds: Array<string>,
|
||||||
attachment: AttachmentType
|
attachment: AttachmentType
|
||||||
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
|
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
|
||||||
return async dispatch => {
|
return async dispatch => {
|
||||||
await doSendStoryMessage(listIds, attachment);
|
await doSendStoryMessage(listIds, conversationIds, attachment);
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'NOOP',
|
type: 'NOOP',
|
||||||
|
|
|
@ -18,12 +18,15 @@ import {
|
||||||
conversationQueueJobEnum,
|
conversationQueueJobEnum,
|
||||||
} from '../jobs/conversationJobQueue';
|
} from '../jobs/conversationJobQueue';
|
||||||
import { formatJobForInsert } from '../jobs/formatJobForInsert';
|
import { formatJobForInsert } from '../jobs/formatJobForInsert';
|
||||||
|
import { getRecipients } from './getRecipients';
|
||||||
import { getSignalConnections } from './getSignalConnections';
|
import { getSignalConnections } from './getSignalConnections';
|
||||||
import { incrementMessageCounter } from './incrementMessageCounter';
|
import { incrementMessageCounter } from './incrementMessageCounter';
|
||||||
|
import { isGroupV2 } from './whatTypeOfConversation';
|
||||||
import { isNotNil } from './isNotNil';
|
import { isNotNil } from './isNotNil';
|
||||||
|
|
||||||
export async function sendStoryMessage(
|
export async function sendStoryMessage(
|
||||||
listIds: Array<string>,
|
listIds: Array<string>,
|
||||||
|
conversationIds: Array<string>,
|
||||||
attachment: AttachmentType
|
attachment: AttachmentType
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { messaging } = window.textsecure;
|
const { messaging } = window.textsecure;
|
||||||
|
@ -41,9 +44,9 @@ export async function sendStoryMessage(
|
||||||
)
|
)
|
||||||
).filter(isNotNil);
|
).filter(isNotNil);
|
||||||
|
|
||||||
if (!distributionLists.length) {
|
if (!distributionLists.length && !conversationIds.length) {
|
||||||
log.info(
|
log.warn(
|
||||||
'stories.sendStoryMessage: no distribution lists found for',
|
'stories.sendStoryMessage: Dropping send. no conversations to send to and no distribution lists found for',
|
||||||
listIds
|
listIds
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
@ -127,43 +130,115 @@ export async function sendStoryMessage(
|
||||||
|
|
||||||
// * Gather all the job data we'll be sending to the sendStory job
|
// * Gather all the job data we'll be sending to the sendStory job
|
||||||
// * Create the message for each distribution list
|
// * Create the message for each distribution list
|
||||||
const messagesToSave: Array<MessageAttributesType> = await Promise.all(
|
const distributionListMessages: Array<MessageAttributesType> =
|
||||||
distributionLists.map(async distributionList => {
|
await Promise.all(
|
||||||
const sendStateByConversationId = sendStateByListId.get(
|
distributionLists.map(async distributionList => {
|
||||||
distributionList.id
|
const sendStateByConversationId = sendStateByListId.get(
|
||||||
);
|
|
||||||
|
|
||||||
if (!sendStateByConversationId) {
|
|
||||||
log.warn(
|
|
||||||
'stories.sendStoryMessage: No sendStateByConversationId for distribution list',
|
|
||||||
distributionList.id
|
distributionList.id
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!sendStateByConversationId) {
|
||||||
|
log.warn(
|
||||||
|
'stories.sendStoryMessage: No sendStateByConversationId for distribution list',
|
||||||
|
distributionList.id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return window.Signal.Migrations.upgradeMessageSchema({
|
||||||
|
attachments,
|
||||||
|
conversationId: ourConversation.id,
|
||||||
|
expireTimer: DAY / SECOND,
|
||||||
|
id: UUID.generate().toString(),
|
||||||
|
readStatus: ReadStatus.Read,
|
||||||
|
received_at: incrementMessageCounter(),
|
||||||
|
received_at_ms: timestamp,
|
||||||
|
seenStatus: SeenStatus.NotApplicable,
|
||||||
|
sendStateByConversationId,
|
||||||
|
sent_at: timestamp,
|
||||||
|
source: window.textsecure.storage.user.getNumber(),
|
||||||
|
sourceUuid: window.textsecure.storage.user.getUuid()?.toString(),
|
||||||
|
storyDistributionListId: distributionList.id,
|
||||||
|
timestamp,
|
||||||
|
type: 'story',
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const groupV2MessagesByConversationId = new Map<
|
||||||
|
string,
|
||||||
|
MessageAttributesType
|
||||||
|
>();
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
conversationIds.map(async conversationId => {
|
||||||
|
const group = window.ConversationController.get(conversationId);
|
||||||
|
|
||||||
|
if (!group) {
|
||||||
|
log.warn(
|
||||||
|
'stories.sendStoryMessage: No group found for id',
|
||||||
|
conversationId
|
||||||
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return window.Signal.Migrations.upgradeMessageSchema({
|
if (!isGroupV2(group.attributes)) {
|
||||||
attachments,
|
log.warn(
|
||||||
conversationId: ourConversation.id,
|
'stories.sendStoryMessage: Conversation we tried to send to is not a groupV2',
|
||||||
expireTimer: DAY / SECOND,
|
conversationId
|
||||||
id: UUID.generate().toString(),
|
);
|
||||||
readStatus: ReadStatus.Read,
|
return;
|
||||||
received_at: incrementMessageCounter(),
|
}
|
||||||
received_at_ms: timestamp,
|
|
||||||
seenStatus: SeenStatus.NotApplicable,
|
const myId = window.ConversationController.getOurConversationIdOrThrow();
|
||||||
sendStateByConversationId,
|
const sendState = {
|
||||||
sent_at: timestamp,
|
status: SendStatus.Pending,
|
||||||
source: window.textsecure.storage.user.getNumber(),
|
updatedAt: timestamp,
|
||||||
sourceUuid: window.textsecure.storage.user.getUuid()?.toString(),
|
};
|
||||||
storyDistributionListId: distributionList.id,
|
|
||||||
timestamp,
|
const sendStateByConversationId = getRecipients(group.attributes).reduce(
|
||||||
type: 'story',
|
(acc, id) => {
|
||||||
});
|
const conversation = window.ConversationController.get(id);
|
||||||
|
if (!conversation) {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...acc,
|
||||||
|
[conversation.id]: sendState,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[myId]: sendState,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const messageAttributes =
|
||||||
|
await window.Signal.Migrations.upgradeMessageSchema({
|
||||||
|
attachments,
|
||||||
|
conversationId,
|
||||||
|
expireTimer: DAY / SECOND,
|
||||||
|
id: UUID.generate().toString(),
|
||||||
|
readStatus: ReadStatus.Read,
|
||||||
|
received_at: incrementMessageCounter(),
|
||||||
|
received_at_ms: timestamp,
|
||||||
|
seenStatus: SeenStatus.NotApplicable,
|
||||||
|
sendStateByConversationId,
|
||||||
|
sent_at: timestamp,
|
||||||
|
source: window.textsecure.storage.user.getNumber(),
|
||||||
|
sourceUuid: window.textsecure.storage.user.getUuid()?.toString(),
|
||||||
|
timestamp,
|
||||||
|
type: 'story',
|
||||||
|
});
|
||||||
|
|
||||||
|
groupV2MessagesByConversationId.set(conversationId, messageAttributes);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// For distribution lists:
|
||||||
// * Save the message model
|
// * Save the message model
|
||||||
// * Add the message to the conversation
|
// * Add the message to the conversation
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
messagesToSave.map(messageAttributes => {
|
distributionListMessages.map(messageAttributes => {
|
||||||
const model = new window.Whisper.Message(messageAttributes);
|
const model = new window.Whisper.Message(messageAttributes);
|
||||||
const message = window.MessageController.register(model.id, model);
|
const message = window.MessageController.register(model.id, model);
|
||||||
|
|
||||||
|
@ -177,13 +252,14 @@ export async function sendStoryMessage(
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// * Send to the distribution lists
|
||||||
// * Place into job queue
|
// * Place into job queue
|
||||||
// * Save the job
|
// * Save the job
|
||||||
await conversationJobQueue.add(
|
await conversationJobQueue.add(
|
||||||
{
|
{
|
||||||
type: conversationQueueJobEnum.enum.Story,
|
type: conversationQueueJobEnum.enum.Story,
|
||||||
conversationId: ourConversation.id,
|
conversationId: ourConversation.id,
|
||||||
messageIds: messagesToSave.map(m => m.id),
|
messageIds: distributionListMessages.map(m => m.id),
|
||||||
timestamp,
|
timestamp,
|
||||||
},
|
},
|
||||||
async jobToInsert => {
|
async jobToInsert => {
|
||||||
|
@ -191,4 +267,45 @@ export async function sendStoryMessage(
|
||||||
await dataInterface.insertJob(formatJobForInsert(jobToInsert));
|
await dataInterface.insertJob(formatJobForInsert(jobToInsert));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// * Send to groups
|
||||||
|
// * Save the message models
|
||||||
|
// * Add message to group conversation
|
||||||
|
await Promise.all(
|
||||||
|
conversationIds.map(conversationId => {
|
||||||
|
const messageAttributes =
|
||||||
|
groupV2MessagesByConversationId.get(conversationId);
|
||||||
|
|
||||||
|
if (!messageAttributes) {
|
||||||
|
log.warn(
|
||||||
|
'stories.sendStoryMessage: Trying to send a group story but it did not exist? This is unexpected. Not sending.',
|
||||||
|
conversationId
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return conversationJobQueue.add(
|
||||||
|
{
|
||||||
|
type: conversationQueueJobEnum.enum.Story,
|
||||||
|
conversationId,
|
||||||
|
messageIds: [messageAttributes.id],
|
||||||
|
timestamp,
|
||||||
|
},
|
||||||
|
async jobToInsert => {
|
||||||
|
const model = new window.Whisper.Message(messageAttributes);
|
||||||
|
const message = window.MessageController.register(model.id, model);
|
||||||
|
|
||||||
|
const conversation = message.getConversation();
|
||||||
|
conversation?.addSingleMessage(model, { isJustSent: true });
|
||||||
|
|
||||||
|
log.info(`stories.sendStoryMessage: saving message ${message.id}`);
|
||||||
|
await dataInterface.saveMessage(message.attributes, {
|
||||||
|
forceSave: true,
|
||||||
|
jobToInsert,
|
||||||
|
ourUuid: window.textsecure.storage.user.getCheckedUuid().toString(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue