Skip to content

Commit

Permalink
Story send: Send sync message even in partial failure
Browse files Browse the repository at this point in the history
  • Loading branch information
scottnonnenberg-signal committed Oct 15, 2022
1 parent 2d5c154 commit 0e49f79
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 30 deletions.
3 changes: 1 addition & 2 deletions ts/background.ts
Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down
88 changes: 69 additions & 19 deletions ts/jobs/helpers/sendStory.ts
Expand Up @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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, {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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({
Expand Down
10 changes: 7 additions & 3 deletions ts/state/selectors/conversations.ts
Expand Up @@ -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)
);
}
);
Expand All @@ -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)
);
}
);
Expand Down
8 changes: 3 additions & 5 deletions ts/util/sendDeleteForEveryoneMessage.ts
Expand Up @@ -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({
Expand Down
5 changes: 4 additions & 1 deletion ts/util/sendStoryMessage.ts
Expand Up @@ -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(),
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -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(),
Expand Down

0 comments on commit 0e49f79

Please sign in to comment.