Support for message retry requests

This commit is contained in:
Scott Nonnenberg 2021-05-28 12:11:19 -07:00 committed by GitHub
parent 28f016ce48
commit ee513a1965
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 1996 additions and 359 deletions

View file

@ -139,6 +139,8 @@ export class ConversationModel extends window.Backbone
throttledFetchSMSOnlyUUID?: () => Promise<void> | void;
throttledMaybeMigrateV1Group?: () => Promise<void> | void;
typingRefreshTimer?: NodeJS.Timer | null;
typingPauseTimer?: NodeJS.Timer | null;
@ -304,7 +306,11 @@ export class ConversationModel extends window.Backbone
this.isFetchingUUID = this.isSMSOnly();
this.throttledFetchSMSOnlyUUID = window._.throttle(
this.fetchSMSOnlyUUID,
this.fetchSMSOnlyUUID.bind(this),
FIVE_MINUTES
);
this.throttledMaybeMigrateV1Group = window._.throttle(
this.maybeMigrateV1Group.bind(this),
FIVE_MINUTES
);
@ -811,6 +817,10 @@ export class ConversationModel extends window.Backbone
}
setRegistered(): void {
if (this.get('discoveredUnregisteredAt') === undefined) {
return;
}
window.log.info(
`Conversation ${this.idForLogging()} is registered once again`
);
@ -1193,15 +1203,18 @@ export class ConversationModel extends window.Backbone
}
);
const {
ContentHint,
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
const sendOptions = await this.getSendOptions();
if (this.isPrivate()) {
const silent = true;
this.wrapSend(
window.textsecure.messaging.sendMessageProtoAndWait(
timestamp,
groupMembers,
contentMessage,
silent,
ContentHint.SUPPLEMENTARY,
undefined,
{
...sendOptions,
online: true,
@ -1211,6 +1224,7 @@ export class ConversationModel extends window.Backbone
} else {
this.wrapSend(
window.Signal.Util.sendContentMessageToGroup({
contentHint: ContentHint.SUPPLEMENTARY,
contentMessage,
conversation: this,
online: true,
@ -2438,7 +2452,8 @@ export class ConversationModel extends window.Backbone
async addChatSessionRefreshed(receivedAt: number): Promise<void> {
window.log.info(
`addChatSessionRefreshed: adding for ${this.idForLogging()}`
`addChatSessionRefreshed: adding for ${this.idForLogging()}`,
{ receivedAt }
);
const message = ({
@ -2466,6 +2481,43 @@ export class ConversationModel extends window.Backbone
this.trigger('newmessage', model);
}
async addDeliveryIssue(
receivedAt: number,
senderUuid: string
): Promise<void> {
window.log.info(`addDeliveryIssue: adding for ${this.idForLogging()}`, {
receivedAt,
senderUuid,
});
const message = ({
conversationId: this.id,
type: 'delivery-issue',
sourceUuid: senderUuid,
sent_at: receivedAt,
received_at: window.Signal.Util.incrementMessageCounter(),
received_at_ms: receivedAt,
unread: 1,
// TODO: DESKTOP-722
// this type does not fully implement the interface it is expected to
} as unknown) as typeof window.Whisper.MessageAttributesType;
const id = await window.Signal.Data.saveMessage(message, {
Message: window.Whisper.Message,
});
const model = window.MessageController.register(
id,
new window.Whisper.Message({
...message,
id,
})
);
this.trigger('newmessage', model);
await this.notify(model);
}
async addKeyChange(keyChangedId: string): Promise<void> {
window.log.info(
'adding key change advisory for',
@ -3108,6 +3160,10 @@ export class ConversationModel extends window.Backbone
profileKey = await ourProfileKeyService.get();
}
const {
ContentHint,
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
if (this.isPrivate()) {
return window.textsecure.messaging.sendMessageToIdentifier(
destination,
@ -3120,6 +3176,8 @@ export class ConversationModel extends window.Backbone
targetTimestamp,
timestamp,
undefined, // expireTimer
ContentHint.SUPPLEMENTARY,
undefined, // groupId
profileKey,
options
);
@ -3134,6 +3192,7 @@ export class ConversationModel extends window.Backbone
profileKey,
},
this,
ContentHint.SUPPLEMENTARY,
options
);
})();
@ -3254,6 +3313,9 @@ export class ConversationModel extends window.Backbone
}
const options = await this.getSendOptions();
const {
ContentHint,
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
const promise = (() => {
if (this.isPrivate()) {
@ -3268,6 +3330,8 @@ export class ConversationModel extends window.Backbone
undefined, // deletedForEveryoneTimestamp
timestamp,
expireTimer,
ContentHint.SUPPLEMENTARY,
undefined, // groupId
profileKey,
options
);
@ -3285,6 +3349,7 @@ export class ConversationModel extends window.Backbone
profileKey,
},
this,
ContentHint.SUPPLEMENTARY,
options
);
})();
@ -3492,6 +3557,9 @@ export class ConversationModel extends window.Backbone
const conversationType = this.get('type');
const options = await this.getSendOptions();
const {
ContentHint,
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
let promise;
if (conversationType === Message.GROUP) {
@ -3510,6 +3578,7 @@ export class ConversationModel extends window.Backbone
mentions,
},
this,
ContentHint.RESENDABLE,
options
);
} else {
@ -3524,6 +3593,8 @@ export class ConversationModel extends window.Backbone
undefined, // deletedForEveryoneTimestamp
now,
expireTimer,
ContentHint.RESENDABLE,
undefined, // groupId
profileKey,
options
);

View file

@ -41,6 +41,7 @@ import {
import { PropsData as SafetyNumberNotificationProps } from '../components/conversation/SafetyNumberNotification';
import { PropsData as VerificationNotificationProps } from '../components/conversation/VerificationNotification';
import { PropsDataType as GroupV1MigrationPropsType } from '../components/conversation/GroupV1Migration';
import { PropsDataType as DeliveryIssuePropsType } from '../components/conversation/DeliveryIssueNotification';
import {
PropsData as GroupNotificationProps,
ChangeType,
@ -132,6 +133,10 @@ type MessageBubbleProps =
type: 'chatSessionRefreshed';
data: null;
}
| {
type: 'deliveryIssue';
data: DeliveryIssuePropsType;
}
| {
type: 'message';
data: PropsForMessage;
@ -407,6 +412,12 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
data: null,
};
}
if (this.isDeliveryIssue()) {
return {
type: 'deliveryIssue',
data: this.getPropsForDeliveryIssue(),
};
}
return {
type: 'message',
@ -581,6 +592,10 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
return this.get('type') === 'chat-session-refreshed';
}
isDeliveryIssue(): boolean {
return this.get('type') === 'delivery-issue';
}
isProfileChange(): boolean {
return this.get('type') === 'profile-change';
}
@ -874,6 +889,14 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
}
}
getPropsForDeliveryIssue(): DeliveryIssuePropsType {
const sender = this.getContact()?.format();
return {
sender,
};
}
getPropsForProfileChange(): ProfileChangeNotificationPropsType {
const change = this.get('profileChange');
const changedId = this.get('changedId');
@ -1359,6 +1382,13 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
}
getNotificationData(): { emoji?: string; text: string } {
if (this.isDeliveryIssue()) {
return {
emoji: '⚠️',
text: window.i18n('DeliveryIssue--preview'),
};
}
if (this.isChatSessionRefreshed()) {
return {
emoji: '🔁',
@ -1893,6 +1923,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
// Rendered sync messages
const isCallHistory = this.isCallHistory();
const isChatSessionRefreshed = this.isChatSessionRefreshed();
const isDeliveryIssue = this.isDeliveryIssue();
const isGroupUpdate = this.isGroupUpdate();
const isGroupV2Change = this.isGroupV2Change();
const isEndSession = this.isEndSession();
@ -1922,6 +1953,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
// Rendered sync messages
isCallHistory ||
isChatSessionRefreshed ||
isDeliveryIssue ||
isGroupUpdate ||
isGroupV2Change ||
isEndSession ||
@ -2216,6 +2248,10 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
let promise;
const options = await conversation.getSendOptions();
const {
ContentHint,
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
if (conversation.isPrivate()) {
const [identifier] = recipients;
promise = window.textsecure.messaging.sendMessageToIdentifier(
@ -2229,6 +2265,8 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
this.get('deletedForEveryoneTimestamp'),
this.get('sent_at'),
this.get('expireTimer'),
ContentHint.RESENDABLE,
undefined, // groupId
profileKey,
options
);
@ -2271,6 +2309,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
groupV1,
},
conversation,
ContentHint.RESENDABLE,
options,
partialSend
);
@ -2403,7 +2442,13 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
async resend(identifier: string): Promise<void | null | Array<void>> {
const error = this.removeOutgoingErrors(identifier);
if (!error) {
window.log.warn('resend: requested number was not present in errors');
window.log.warn(
'resend: requested number was not present in errors. continuing.'
);
}
if (this.isErased()) {
window.log.warn('resend: message is erased; refusing to resend');
return null;
}
@ -2431,7 +2476,6 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
body,
deletedForEveryoneTimestamp: this.get('deletedForEveryoneTimestamp'),
expireTimer: this.get('expireTimer'),
// flags
mentions: this.get('bodyRanges'),
preview: previewWithData,
profileKey,
@ -2444,22 +2488,59 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
return this.sendSyncMessageOnly(dataMessage);
}
const {
ContentHint,
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
const parentConversation = this.getConversation();
const groupId = parentConversation?.get('groupId');
const {
wrap,
sendOptions,
} = await window.ConversationController.prepareForSend(identifier);
const promise = window.textsecure.messaging.sendMessageToIdentifier(
identifier,
body,
const group =
groupId && parentConversation?.isGroupV1()
? {
id: groupId,
type: window.textsecure.protobuf.GroupContext.Type.DELIVER,
}
: undefined;
const timestamp = this.get('sent_at');
const contentMessage = await window.textsecure.messaging.getContentMessage({
attachments,
quoteWithData,
previewWithData,
stickerWithData,
null,
this.get('deletedForEveryoneTimestamp'),
this.get('sent_at'),
this.get('expireTimer'),
profileKey,
body,
expireTimer: this.get('expireTimer'),
group,
groupV2: parentConversation?.getGroupV2Info(),
preview: previewWithData,
quote: quoteWithData,
mentions: this.get('bodyRanges'),
recipients: [identifier],
sticker: stickerWithData,
timestamp,
});
if (parentConversation) {
const senderKeyInfo = parentConversation.get('senderKeyInfo');
if (senderKeyInfo && senderKeyInfo.distributionId) {
const senderKeyDistributionMessage = await window.textsecure.messaging.getSenderKeyDistributionMessage(
senderKeyInfo.distributionId
);
window.dcodeIO.ByteBuffer.wrap(
window.Signal.Crypto.typedArrayToArrayBuffer(
senderKeyDistributionMessage.serialize()
)
);
}
}
const promise = window.textsecure.messaging.sendMessageProtoAndWait(
timestamp,
[identifier],
contentMessage,
ContentHint.RESENDABLE,
groupId && parentConversation?.isGroupV2() ? groupId : undefined,
sendOptions
);
@ -2506,7 +2587,10 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
sent_to: _.union(sentTo, result.successfulIdentifiers),
sent: true,
expirationStartTimestamp: Date.now(),
unidentifiedDeliveries: result.unidentifiedDeliveries,
unidentifiedDeliveries: _.union(
this.get('unidentifiedDeliveries') || [],
result.unidentifiedDeliveries
),
});
if (!this.doNotSave) {
@ -2595,7 +2679,10 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
sent_to: _.union(sentTo, result.successfulIdentifiers),
sent: true,
expirationStartTimestamp,
unidentifiedDeliveries: result.unidentifiedDeliveries,
unidentifiedDeliveries: _.union(
this.get('unidentifiedDeliveries') || [],
result.unidentifiedDeliveries
),
});
promises.push(this.sendSyncMessage());
} else if (result.errors) {
@ -3452,6 +3539,24 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
}
}
// Now check for decryption error placeholders
const { retryPlaceholders } = window.Signal.Services;
if (retryPlaceholders) {
const item = await retryPlaceholders.findByMessageAndRemove(
conversationId,
message.get('sent_at')
);
if (item) {
window.log.info(
`handleDataMessage: found retry placeholder. Updating ${message.idForLogging()} received_at/received_at_ms`
);
message.set({
received_at: item.receivedAtCounter,
received_at_ms: item.receivedAt,
});
}
}
// GroupV2
if (initialMessage.groupV2) {