Story send: Send sync message even in partial failure
This commit is contained in:
parent
2d5c154e2a
commit
0e49f7906d
5 changed files with 84 additions and 30 deletions
|
@ -228,6 +228,7 @@ export async function startApp(): Promise<void> {
|
||||||
hasStoriesDisabled: window.storage.get('hasStoriesDisabled', false),
|
hasStoriesDisabled: window.storage.get('hasStoriesDisabled', false),
|
||||||
});
|
});
|
||||||
window.textsecure.server = server;
|
window.textsecure.server = server;
|
||||||
|
window.textsecure.messaging = new window.textsecure.MessageSender(server);
|
||||||
|
|
||||||
initializeAllJobQueues({
|
initializeAllJobQueues({
|
||||||
server,
|
server,
|
||||||
|
@ -2084,8 +2085,6 @@ export async function startApp(): Promise<void> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.textsecure.messaging = new window.textsecure.MessageSender(server);
|
|
||||||
|
|
||||||
// Update our profile key in the conversation if we just got linked.
|
// Update our profile key in the conversation if we just got linked.
|
||||||
const profileKey = await ourProfileKeyService.get();
|
const profileKey = await ourProfileKeyService.get();
|
||||||
if (firstRun && profileKey) {
|
if (firstRun && profileKey) {
|
||||||
|
|
|
@ -34,6 +34,7 @@ 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';
|
||||||
import { sendContentMessageToGroup } from '../../util/sendToGroup';
|
import { sendContentMessageToGroup } from '../../util/sendToGroup';
|
||||||
|
import { SendMessageChallengeError } from '../../textsecure/Errors';
|
||||||
|
|
||||||
export async function sendStory(
|
export async function sendStory(
|
||||||
conversation: ConversationModel,
|
conversation: ConversationModel,
|
||||||
|
@ -55,6 +56,16 @@ export async function sendStory(
|
||||||
return;
|
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
|
// We want to generate the StoryMessage proto once at the top level so we
|
||||||
// can reuse it but first we'll need textAttachment | fileAttachment.
|
// can reuse it but first we'll need textAttachment | fileAttachment.
|
||||||
// This function pulls off the attachment and generates the proto from the
|
// This function pulls off the attachment and generates the proto from the
|
||||||
|
@ -153,9 +164,11 @@ export async function sendStory(
|
||||||
|
|
||||||
let isSyncMessageUpdate = false;
|
let isSyncMessageUpdate = false;
|
||||||
|
|
||||||
// Send to all distribution lists
|
// Note: We capture errors here so we are sure to wait for every send process to
|
||||||
await Promise.all(
|
// complete, and so we can send a sync message afterwards if we sent the story
|
||||||
messageIds.map(async messageId => {
|
// successfully to at least one recipient.
|
||||||
|
const sendResults = await Promise.allSettled(
|
||||||
|
messageIds.map(async (messageId: string): Promise<void> => {
|
||||||
const message = await getMessageById(messageId);
|
const message = await getMessageById(messageId);
|
||||||
if (!message) {
|
if (!message) {
|
||||||
log.info(
|
log.info(
|
||||||
|
@ -322,9 +335,8 @@ export async function sendStory(
|
||||||
urgent: false,
|
urgent: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Do not send sync messages for distribution lists since that's sent
|
// Don't send normal sync messages; a story sync is sent at the end of the process
|
||||||
// in bulk at the end.
|
message.doNotSendSyncMessage = true;
|
||||||
message.doNotSendSyncMessage = Boolean(distributionList);
|
|
||||||
|
|
||||||
const messageSendPromise = message.send(
|
const messageSendPromise = message.send(
|
||||||
handleMessageSend(innerPromise, {
|
handleMessageSend(innerPromise, {
|
||||||
|
@ -391,6 +403,26 @@ export async function sendStory(
|
||||||
}
|
}
|
||||||
} catch (thrownError: unknown) {
|
} catch (thrownError: unknown) {
|
||||||
const errors = [thrownError, ...messageSendErrors];
|
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({
|
await handleMultipleSendErrors({
|
||||||
errors,
|
errors,
|
||||||
isFinalAttempt,
|
isFinalAttempt,
|
||||||
|
@ -470,21 +502,39 @@ export async function sendStory(
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const options = await getSendOptions(conversation.attributes, {
|
if (storyMessageRecipients.length === 0) {
|
||||||
syncMessage: true,
|
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({
|
await messaging.sendSyncMessage({
|
||||||
destination: conversation.get('e164'),
|
// Note: these two fields will be undefined if we're sending to a group
|
||||||
destinationUuid: conversation.get('uuid'),
|
destination: conversation.get('e164'),
|
||||||
storyMessage: originalStoryMessage,
|
destinationUuid: conversation.get('uuid'),
|
||||||
storyMessageRecipients,
|
storyMessage: originalStoryMessage,
|
||||||
expirationStartTimestamp: null,
|
storyMessageRecipients,
|
||||||
isUpdate: isSyncMessageUpdate,
|
expirationStartTimestamp: null,
|
||||||
options,
|
isUpdate: isSyncMessageUpdate,
|
||||||
timestamp,
|
options,
|
||||||
urgent: false,
|
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({
|
function getMessageRecipients({
|
||||||
|
|
|
@ -548,7 +548,9 @@ export const getNonGroupStories = createSelector(
|
||||||
conversationIdsWithStories: Set<string>
|
conversationIdsWithStories: Set<string>
|
||||||
): Array<ConversationType> => {
|
): Array<ConversationType> => {
|
||||||
return groups.filter(
|
return groups.filter(
|
||||||
group => !isGroupInStoryMode(group, conversationIdsWithStories)
|
group =>
|
||||||
|
!isGroupV2(group) ||
|
||||||
|
!isGroupInStoryMode(group, conversationIdsWithStories)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -560,8 +562,10 @@ export const getGroupStories = createSelector(
|
||||||
conversationLookup: ConversationLookupType,
|
conversationLookup: ConversationLookupType,
|
||||||
conversationIdsWithStories: Set<string>
|
conversationIdsWithStories: Set<string>
|
||||||
): Array<ConversationType> => {
|
): Array<ConversationType> => {
|
||||||
return Object.values(conversationLookup).filter(conversation =>
|
return Object.values(conversationLookup).filter(
|
||||||
isGroupInStoryMode(conversation, conversationIdsWithStories)
|
conversation =>
|
||||||
|
isGroupV2(conversation) &&
|
||||||
|
isGroupInStoryMode(conversation, conversationIdsWithStories)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -40,11 +40,9 @@ export async function sendDeleteForEveryoneMessage(
|
||||||
const messageModel = window.MessageController.register(messageId, message);
|
const messageModel = window.MessageController.register(messageId, message);
|
||||||
|
|
||||||
const timestamp = Date.now();
|
const timestamp = Date.now();
|
||||||
if (
|
const maxDuration = deleteForEveryoneDuration || THREE_HOURS;
|
||||||
timestamp - targetTimestamp >
|
if (timestamp - targetTimestamp > maxDuration) {
|
||||||
(deleteForEveryoneDuration || THREE_HOURS)
|
throw new Error(`Cannot send DOE for a message older than ${maxDuration}`);
|
||||||
) {
|
|
||||||
throw new Error('Cannot send DOE for a message older than three hours');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
messageModel.set({
|
messageModel.set({
|
||||||
|
|
|
@ -156,6 +156,7 @@ export async function sendStoryMessage(
|
||||||
attachments,
|
attachments,
|
||||||
conversationId: ourConversation.id,
|
conversationId: ourConversation.id,
|
||||||
expireTimer: DAY / SECOND,
|
expireTimer: DAY / SECOND,
|
||||||
|
expirationStartTimestamp: Date.now(),
|
||||||
id: UUID.generate().toString(),
|
id: UUID.generate().toString(),
|
||||||
readStatus: ReadStatus.Read,
|
readStatus: ReadStatus.Read,
|
||||||
received_at: incrementMessageCounter(),
|
received_at: incrementMessageCounter(),
|
||||||
|
@ -205,7 +206,8 @@ export async function sendStoryMessage(
|
||||||
return;
|
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 myId = window.ConversationController.getOurConversationIdOrThrow();
|
||||||
const sendState = {
|
const sendState = {
|
||||||
|
@ -236,6 +238,7 @@ export async function sendStoryMessage(
|
||||||
canReplyToStory: true,
|
canReplyToStory: true,
|
||||||
conversationId,
|
conversationId,
|
||||||
expireTimer: DAY / SECOND,
|
expireTimer: DAY / SECOND,
|
||||||
|
expirationStartTimestamp: Date.now(),
|
||||||
id: UUID.generate().toString(),
|
id: UUID.generate().toString(),
|
||||||
readStatus: ReadStatus.Read,
|
readStatus: ReadStatus.Read,
|
||||||
received_at: incrementMessageCounter(),
|
received_at: incrementMessageCounter(),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue