diff --git a/ts/models/messages.ts b/ts/models/messages.ts index 5d4c84864..499c9202f 100644 --- a/ts/models/messages.ts +++ b/ts/models/messages.ts @@ -1401,7 +1401,13 @@ export class MessageModel extends window.Backbone.Model { ...(this.get('sendStateByConversationId') || {}), }; + const sendIsNotFinal = + 'sendIsNotFinal' in result.value && result.value.sendIsNotFinal; + const sendIsFinal = !sendIsNotFinal; + + // Capture successful sends const successfulIdentifiers: Array = + sendIsFinal && 'successfulIdentifiers' in result.value && Array.isArray(result.value.successfulIdentifiers) ? result.value.successfulIdentifiers @@ -1435,9 +1441,11 @@ export class MessageModel extends window.Backbone.Model { } }); + // Integrate sends via sealed sender const previousUnidentifiedDeliveries = this.get('unidentifiedDeliveries') || []; const newUnidentifiedDeliveries = + sendIsFinal && 'unidentifiedDeliveries' in result.value && Array.isArray(result.value.unidentifiedDeliveries) ? result.value.unidentifiedDeliveries @@ -1445,6 +1453,7 @@ export class MessageModel extends window.Backbone.Model { const promises: Array> = []; + // Process errors let errors: Array; if (result.value instanceof SendMessageProtoError && result.value.errors) { ({ errors } = result.value); @@ -1467,7 +1476,7 @@ export class MessageModel extends window.Backbone.Model { window.ConversationController.get(error.identifier) || window.ConversationController.get(error.number); - if (conversation && !saveErrors) { + if (conversation && !saveErrors && sendIsFinal) { const previousSendState = getOwn( sendStateByConversationId, conversation.id diff --git a/ts/textsecure/Errors.ts b/ts/textsecure/Errors.ts index 58f23fd2c..801017313 100644 --- a/ts/textsecure/Errors.ts +++ b/ts/textsecure/Errors.ts @@ -204,6 +204,8 @@ export class SendMessageProtoError extends Error implements CallbackResultType { public readonly recipients?: Record>; + public readonly sendIsNotFinal?: boolean; + constructor({ successfulIdentifiers, failoverIdentifiers, @@ -214,6 +216,7 @@ export class SendMessageProtoError extends Error implements CallbackResultType { contentProto, timestamp, recipients, + sendIsNotFinal, }: CallbackResultType) { super(`SendMessageProtoError: ${SendMessageProtoError.getMessage(errors)}`); @@ -226,6 +229,7 @@ export class SendMessageProtoError extends Error implements CallbackResultType { this.contentProto = contentProto; this.timestamp = timestamp; this.recipients = recipients; + this.sendIsNotFinal = sendIsNotFinal; } protected static getMessage(errors: CallbackResultType['errors']): string { diff --git a/ts/textsecure/Types.d.ts b/ts/textsecure/Types.d.ts index 7ea6c322d..0459ac377 100644 --- a/ts/textsecure/Types.d.ts +++ b/ts/textsecure/Types.d.ts @@ -251,6 +251,10 @@ export interface CallbackResultType { unidentifiedDeliveries?: Array; dataMessage?: Uint8Array; + // If this send is not the final step in a multi-step send, we shouldn't treat its + // results we would treat a one-step send. + sendIsNotFinal?: boolean; + // Fields necessary for send log save contentHint?: number; contentProto?: Uint8Array; diff --git a/ts/util/sendToGroup.ts b/ts/util/sendToGroup.ts index bd66540e3..41b8dcd1b 100644 --- a/ts/util/sendToGroup.ts +++ b/ts/util/sendToGroup.ts @@ -412,18 +412,31 @@ export async function sendToGroupViaSenderKey(options: { newToMemberUuids.length } members: ${JSON.stringify(newToMemberUuids)}` ); - await handleMessageSend( - window.textsecure.messaging.sendSenderKeyDistributionMessage( - { - contentHint: ContentHint.RESENDABLE, - distributionId, - groupId, - identifiers: newToMemberUuids, - }, - sendOptions ? { ...sendOptions, online: false } : undefined - ), - { messageIds: [], sendType: 'senderKeyDistributionMessage' } - ); + try { + await handleMessageSend( + window.textsecure.messaging.sendSenderKeyDistributionMessage( + { + contentHint: ContentHint.RESENDABLE, + distributionId, + groupId, + identifiers: newToMemberUuids, + }, + sendOptions ? { ...sendOptions, online: false } : undefined + ), + { messageIds: [], sendType: 'senderKeyDistributionMessage' } + ); + } catch (error) { + // If we partially fail to send the sender key distribution message (SKDM), we don't + // want the successful SKDM sends to be considered an overall success. + if (error instanceof SendMessageProtoError) { + throw new SendMessageProtoError({ + ...error, + sendIsNotFinal: true, + }); + } + + throw error; + } // Update memberDevices with new devices const updatedMemberDevices = [...memberDevices, ...newToMemberDevices];