Send stories to groups capability

This commit is contained in:
Josh Perez 2022-08-08 23:26:21 -04:00 committed by GitHub
parent 62962e4950
commit 2f5dd73e58
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 308 additions and 163 deletions

View file

@ -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;

View file

@ -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();
}} }}

View file

@ -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>();
if (listId) {
recipientsByUuid.set(uuid, new Set([...distributionListIds, 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(
sendState =>
sendState.isAlreadyIncludedInAnotherDistributionList ||
isSent(sendState.status) isSent(sendState.status)
); );
} }

View file

@ -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',

View file

@ -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,7 +130,8 @@ 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> =
await Promise.all(
distributionLists.map(async distributionList => { distributionLists.map(async distributionList => {
const sendStateByConversationId = sendStateByListId.get( const sendStateByConversationId = sendStateByListId.get(
distributionList.id distributionList.id
@ -160,10 +164,81 @@ export async function sendStoryMessage(
}) })
); );
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;
}
if (!isGroupV2(group.attributes)) {
log.warn(
'stories.sendStoryMessage: Conversation we tried to send to is not a groupV2',
conversationId
);
return;
}
const myId = window.ConversationController.getOurConversationIdOrThrow();
const sendState = {
status: SendStatus.Pending,
updatedAt: timestamp,
};
const sendStateByConversationId = getRecipients(group.attributes).reduce(
(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(),
});
}
);
})
);
} }