Story send: Send sync message even in partial failure

Co-authored-by: Scott Nonnenberg <scott@signal.org>
This commit is contained in:
automated-signal 2022-10-17 09:43:09 -07:00 committed by GitHub
parent 859092a541
commit 28020f00d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 84 additions and 30 deletions

View File

@ -228,6 +228,7 @@ export async function startApp(): Promise<void> {
hasStoriesDisabled: window.storage.get('hasStoriesDisabled', false),
});
window.textsecure.server = server;
window.textsecure.messaging = new window.textsecure.MessageSender(server);
initializeAllJobQueues({
server,
@ -2084,8 +2085,6 @@ export async function startApp(): Promise<void> {
return;
}
window.textsecure.messaging = new window.textsecure.MessageSender(server);
// Update our profile key in the conversation if we just got linked.
const profileKey = await ourProfileKeyService.get();
if (firstRun && profileKey) {

View File

@ -34,6 +34,7 @@ import { isNotNil } from '../../util/isNotNil';
import { isSent } from '../../messages/MessageSendState';
import { ourProfileKeyService } from '../../services/ourProfileKey';
import { sendContentMessageToGroup } from '../../util/sendToGroup';
import { SendMessageChallengeError } from '../../textsecure/Errors';
export async function sendStory(
conversation: ConversationModel,
@ -55,6 +56,16 @@ export async function sendStory(
return;
}
// We can send a story to either:
// 1) the current group, or
// 2) all selected distribution lists (in queue for our own conversationId)
if (!isGroupV2(conversation.attributes) && !isMe(conversation.attributes)) {
log.error(
'stories.sendStory: Conversation is neither groupV2 nor our own. Cannot send.'
);
return;
}
// We want to generate the StoryMessage proto once at the top level so we
// can reuse it but first we'll need textAttachment | fileAttachment.
// This function pulls off the attachment and generates the proto from the
@ -153,9 +164,11 @@ export async function sendStory(
let isSyncMessageUpdate = false;
// Send to all distribution lists
await Promise.all(
messageIds.map(async messageId => {
// Note: We capture errors here so we are sure to wait for every send process to
// complete, and so we can send a sync message afterwards if we sent the story
// successfully to at least one recipient.
const sendResults = await Promise.allSettled(
messageIds.map(async (messageId: string): Promise<void> => {
const message = await getMessageById(messageId);
if (!message) {
log.info(
@ -322,9 +335,8 @@ export async function sendStory(
urgent: false,
});
// Do not send sync messages for distribution lists since that's sent
// in bulk at the end.
message.doNotSendSyncMessage = Boolean(distributionList);
// Don't send normal sync messages; a story sync is sent at the end of the process
message.doNotSendSyncMessage = true;
const messageSendPromise = message.send(
handleMessageSend(innerPromise, {
@ -391,6 +403,26 @@ export async function sendStory(
}
} catch (thrownError: unknown) {
const errors = [thrownError, ...messageSendErrors];
// We need to check for this here because we can only throw one error up to
// conversationJobQueue.
errors.forEach(error => {
if (error instanceof SendMessageChallengeError) {
window.Signal.challengeHandler?.register(
{
conversationId: conversation.id,
createdAt: Date.now(),
retryAt: error.retryAt,
token: error.data?.token,
reason:
'conversationJobQueue.run(' +
`${conversation.idForLogging()}, story, ${timestamp})`,
},
error.data
);
}
});
await handleMultipleSendErrors({
errors,
isFinalAttempt,
@ -470,21 +502,39 @@ export async function sendStory(
});
});
const options = await getSendOptions(conversation.attributes, {
syncMessage: true,
});
if (storyMessageRecipients.length === 0) {
log.warn(
'No successful sends; will not send a sync message for this attempt'
);
} else {
const options = await getSendOptions(conversation.attributes, {
syncMessage: true,
});
messaging.sendSyncMessage({
destination: conversation.get('e164'),
destinationUuid: conversation.get('uuid'),
storyMessage: originalStoryMessage,
storyMessageRecipients,
expirationStartTimestamp: null,
isUpdate: isSyncMessageUpdate,
options,
timestamp,
urgent: false,
await messaging.sendSyncMessage({
// Note: these two fields will be undefined if we're sending to a group
destination: conversation.get('e164'),
destinationUuid: conversation.get('uuid'),
storyMessage: originalStoryMessage,
storyMessageRecipients,
expirationStartTimestamp: null,
isUpdate: isSyncMessageUpdate,
options,
timestamp,
urgent: false,
});
}
// We can only throw one Error up to conversationJobQueue to fail the send
const sendErrors: Array<PromiseRejectedResult> = [];
sendResults.forEach(result => {
if (result.status === 'rejected') {
sendErrors.push(result);
}
});
if (sendErrors.length) {
throw sendErrors[0].reason;
}
}
function getMessageRecipients({

View File

@ -548,7 +548,9 @@ export const getNonGroupStories = createSelector(
conversationIdsWithStories: Set<string>
): Array<ConversationType> => {
return groups.filter(
group => !isGroupInStoryMode(group, conversationIdsWithStories)
group =>
!isGroupV2(group) ||
!isGroupInStoryMode(group, conversationIdsWithStories)
);
}
);
@ -560,8 +562,10 @@ export const getGroupStories = createSelector(
conversationLookup: ConversationLookupType,
conversationIdsWithStories: Set<string>
): Array<ConversationType> => {
return Object.values(conversationLookup).filter(conversation =>
isGroupInStoryMode(conversation, conversationIdsWithStories)
return Object.values(conversationLookup).filter(
conversation =>
isGroupV2(conversation) &&
isGroupInStoryMode(conversation, conversationIdsWithStories)
);
}
);

View File

@ -40,11 +40,9 @@ export async function sendDeleteForEveryoneMessage(
const messageModel = window.MessageController.register(messageId, message);
const timestamp = Date.now();
if (
timestamp - targetTimestamp >
(deleteForEveryoneDuration || THREE_HOURS)
) {
throw new Error('Cannot send DOE for a message older than three hours');
const maxDuration = deleteForEveryoneDuration || THREE_HOURS;
if (timestamp - targetTimestamp > maxDuration) {
throw new Error(`Cannot send DOE for a message older than ${maxDuration}`);
}
messageModel.set({

View File

@ -156,6 +156,7 @@ export async function sendStoryMessage(
attachments,
conversationId: ourConversation.id,
expireTimer: DAY / SECOND,
expirationStartTimestamp: Date.now(),
id: UUID.generate().toString(),
readStatus: ReadStatus.Read,
received_at: incrementMessageCounter(),
@ -205,7 +206,8 @@ export async function sendStoryMessage(
return;
}
const groupTimestamp = timestamp + index;
// We want all of these timestamps to be different from the My Story timestamp.
const groupTimestamp = timestamp + index + 1;
const myId = window.ConversationController.getOurConversationIdOrThrow();
const sendState = {
@ -236,6 +238,7 @@ export async function sendStoryMessage(
canReplyToStory: true,
conversationId,
expireTimer: DAY / SECOND,
expirationStartTimestamp: Date.now(),
id: UUID.generate().toString(),
readStatus: ReadStatus.Read,
received_at: incrementMessageCounter(),