Update message receipt processing & add tests
This commit is contained in:
parent
5e733059b9
commit
c8099171e2
2 changed files with 215 additions and 14 deletions
|
@ -30,7 +30,6 @@ import {
|
||||||
RECEIPT_BATCHER_WAIT_MS,
|
RECEIPT_BATCHER_WAIT_MS,
|
||||||
} from '../types/Receipt';
|
} from '../types/Receipt';
|
||||||
import { drop } from '../util/drop';
|
import { drop } from '../util/drop';
|
||||||
import { strictAssert } from '../util/assert';
|
|
||||||
|
|
||||||
const { deleteSentProtoRecipient } = dataInterface;
|
const { deleteSentProtoRecipient } = dataInterface;
|
||||||
|
|
||||||
|
@ -85,7 +84,6 @@ const processReceiptBatcher = createWaitBatcher({
|
||||||
receipt: MessageReceiptAttributesType
|
receipt: MessageReceiptAttributesType
|
||||||
): void {
|
): void {
|
||||||
const existing = receiptsByMessageId.get(message.id);
|
const existing = receiptsByMessageId.get(message.id);
|
||||||
|
|
||||||
if (!existing) {
|
if (!existing) {
|
||||||
window.MessageCache.toMessageAttributes(message);
|
window.MessageCache.toMessageAttributes(message);
|
||||||
receiptsByMessageId.set(message.id, [receipt]);
|
receiptsByMessageId.set(message.id, [receipt]);
|
||||||
|
@ -146,12 +144,13 @@ const processReceiptBatcher = createWaitBatcher({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [
|
await Promise.all(
|
||||||
messageId,
|
[...receiptsByMessageId.entries()].map(
|
||||||
receiptsForMessage,
|
([messageId, receiptsForMessage]) => {
|
||||||
] of receiptsByMessageId.entries()) {
|
return processReceiptsForMessage(messageId, receiptsForMessage);
|
||||||
drop(processReceiptsForMessage(messageId, receiptsForMessage));
|
}
|
||||||
}
|
)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -230,7 +229,7 @@ function updateMessageWithReceipts(
|
||||||
for (const receipt of receiptsToProcess) {
|
for (const receipt of receiptsToProcess) {
|
||||||
updatedMessage = {
|
updatedMessage = {
|
||||||
...updatedMessage,
|
...updatedMessage,
|
||||||
...updateMessageSendStateWithReceipt(message.id, receipt),
|
...updateMessageSendStateWithReceipt(updatedMessage, receipt),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return { updatedMessage, validReceipts: receiptsToProcess };
|
return { updatedMessage, validReceipts: receiptsToProcess };
|
||||||
|
@ -443,15 +442,11 @@ function getNewSendStateByConversationId(
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateMessageSendStateWithReceipt(
|
function updateMessageSendStateWithReceipt(
|
||||||
messageId: string,
|
message: MessageAttributesType,
|
||||||
receipt: MessageReceiptAttributesType
|
receipt: MessageReceiptAttributesType
|
||||||
): Partial<MessageAttributesType> {
|
): Partial<MessageAttributesType> {
|
||||||
const { messageSentAt } = receipt;
|
const { messageSentAt } = receipt;
|
||||||
|
|
||||||
// Get message from cache to make sure we have most recent
|
|
||||||
const message = window.MessageCache.accessAttributes(messageId);
|
|
||||||
strictAssert(message, 'Message should exist in cache');
|
|
||||||
|
|
||||||
const newAttributes: Partial<MessageAttributesType> = {};
|
const newAttributes: Partial<MessageAttributesType> = {};
|
||||||
|
|
||||||
const newEditHistory = (message.editHistory ?? []).map(edit => {
|
const newEditHistory = (message.editHistory ?? []).map(edit => {
|
||||||
|
|
206
ts/test-electron/MessageReceipts_test.ts
Normal file
206
ts/test-electron/MessageReceipts_test.ts
Normal file
|
@ -0,0 +1,206 @@
|
||||||
|
// Copyright 2023 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import uuid from 'uuid';
|
||||||
|
import { assert } from 'chai';
|
||||||
|
|
||||||
|
import { type AciString, generateAci } from '../types/ServiceId';
|
||||||
|
import type { MessageAttributesType } from '../model-types';
|
||||||
|
import { SendStatus } from '../messages/MessageSendState';
|
||||||
|
import {
|
||||||
|
type MessageReceiptAttributesType,
|
||||||
|
MessageReceiptType,
|
||||||
|
onReceipt,
|
||||||
|
} from '../messageModifiers/MessageReceipts';
|
||||||
|
import { ReadStatus } from '../messages/MessageReadStatus';
|
||||||
|
|
||||||
|
describe('MessageReceipts', () => {
|
||||||
|
let ourAci: AciString;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
ourAci = generateAci();
|
||||||
|
await window.textsecure.storage.put('uuid_id', `${ourAci}.1`);
|
||||||
|
await window.textsecure.storage.put('read-receipt-setting', true);
|
||||||
|
await window.ConversationController.load();
|
||||||
|
});
|
||||||
|
|
||||||
|
function generateReceipt(
|
||||||
|
sourceConversationId: string,
|
||||||
|
messageSentAt: number,
|
||||||
|
type: MessageReceiptType
|
||||||
|
): MessageReceiptAttributesType {
|
||||||
|
return {
|
||||||
|
envelopeId: uuid(),
|
||||||
|
messageSentAt,
|
||||||
|
receiptTimestamp: 1,
|
||||||
|
removeFromMessageReceiverCache: () => null,
|
||||||
|
sourceConversationId,
|
||||||
|
sourceDevice: 1,
|
||||||
|
sourceServiceId: generateAci(),
|
||||||
|
type,
|
||||||
|
wasSentEncrypted: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
it('processes all receipts in a batch', async () => {
|
||||||
|
const id = uuid();
|
||||||
|
const sentAt = Date.now();
|
||||||
|
|
||||||
|
const messageAttributes: MessageAttributesType = {
|
||||||
|
conversationId: uuid(),
|
||||||
|
id,
|
||||||
|
received_at: 1,
|
||||||
|
sent_at: sentAt,
|
||||||
|
timestamp: sentAt,
|
||||||
|
type: 'outgoing',
|
||||||
|
sendStateByConversationId: {
|
||||||
|
aaaa: {
|
||||||
|
status: SendStatus.Sent,
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
},
|
||||||
|
bbbb: {
|
||||||
|
status: SendStatus.Sent,
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
},
|
||||||
|
cccc: {
|
||||||
|
status: SendStatus.Sent,
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
},
|
||||||
|
dddd: {
|
||||||
|
status: SendStatus.Sent,
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await window.Signal.Data.saveMessage(messageAttributes, {
|
||||||
|
forceSave: true,
|
||||||
|
ourAci,
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
onReceipt(generateReceipt('aaaa', sentAt, MessageReceiptType.Delivery)),
|
||||||
|
onReceipt(generateReceipt('bbbb', sentAt, MessageReceiptType.Delivery)),
|
||||||
|
onReceipt(generateReceipt('cccc', sentAt, MessageReceiptType.Read)),
|
||||||
|
onReceipt(generateReceipt('aaaa', sentAt, MessageReceiptType.Read)),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const messageFromDatabase = await window.Signal.Data.getMessageById(id);
|
||||||
|
const savedSendState = messageFromDatabase?.sendStateByConversationId;
|
||||||
|
|
||||||
|
assert.equal(savedSendState?.aaaa.status, SendStatus.Read, 'aaaa');
|
||||||
|
assert.equal(savedSendState?.bbbb.status, SendStatus.Delivered, 'bbbb');
|
||||||
|
assert.equal(savedSendState?.cccc.status, SendStatus.Read, 'cccc');
|
||||||
|
assert.equal(savedSendState?.dddd.status, SendStatus.Sent, 'dddd');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates sendStateByConversationId for edits', async () => {
|
||||||
|
const id = uuid();
|
||||||
|
const sentAt = Date.now();
|
||||||
|
const editedSentAt = sentAt + 1000;
|
||||||
|
const defaultSendState = {
|
||||||
|
aaaa: {
|
||||||
|
status: SendStatus.Sent,
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
},
|
||||||
|
bbbb: {
|
||||||
|
status: SendStatus.Sent,
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
},
|
||||||
|
cccc: {
|
||||||
|
status: SendStatus.Sent,
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
},
|
||||||
|
dddd: {
|
||||||
|
status: SendStatus.Sent,
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const messageAttributes: MessageAttributesType = {
|
||||||
|
conversationId: uuid(),
|
||||||
|
id,
|
||||||
|
received_at: 1,
|
||||||
|
sent_at: sentAt,
|
||||||
|
timestamp: sentAt,
|
||||||
|
editMessageTimestamp: editedSentAt,
|
||||||
|
type: 'outgoing',
|
||||||
|
sendStateByConversationId: defaultSendState,
|
||||||
|
editHistory: [
|
||||||
|
{
|
||||||
|
sendStateByConversationId: defaultSendState,
|
||||||
|
timestamp: editedSentAt,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sendStateByConversationId: defaultSendState,
|
||||||
|
timestamp: sentAt,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
await window.Signal.Data.saveMessage(messageAttributes, {
|
||||||
|
forceSave: true,
|
||||||
|
ourAci,
|
||||||
|
});
|
||||||
|
await window.Signal.Data.saveEditedMessage(messageAttributes, ourAci, {
|
||||||
|
conversationId: messageAttributes.conversationId,
|
||||||
|
messageId: messageAttributes.id,
|
||||||
|
readStatus: ReadStatus.Read,
|
||||||
|
sentAt: editedSentAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
// send receipts for original message
|
||||||
|
onReceipt(generateReceipt('aaaa', sentAt, MessageReceiptType.Delivery)),
|
||||||
|
onReceipt(generateReceipt('bbbb', sentAt, MessageReceiptType.Delivery)),
|
||||||
|
onReceipt(generateReceipt('cccc', sentAt, MessageReceiptType.Read)),
|
||||||
|
onReceipt(generateReceipt('aaaa', sentAt, MessageReceiptType.Read)),
|
||||||
|
|
||||||
|
// and send receipts for edited message
|
||||||
|
onReceipt(
|
||||||
|
generateReceipt('aaaa', editedSentAt, MessageReceiptType.Delivery)
|
||||||
|
),
|
||||||
|
onReceipt(
|
||||||
|
generateReceipt('bbbb', editedSentAt, MessageReceiptType.Delivery)
|
||||||
|
),
|
||||||
|
onReceipt(generateReceipt('cccc', editedSentAt, MessageReceiptType.Read)),
|
||||||
|
onReceipt(generateReceipt('bbbb', editedSentAt, MessageReceiptType.Read)),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const messageFromDatabase = await window.Signal.Data.getMessageById(id);
|
||||||
|
const rootSendState = messageFromDatabase?.sendStateByConversationId;
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
rootSendState,
|
||||||
|
messageFromDatabase?.editHistory?.[0].sendStateByConversationId,
|
||||||
|
'edit history version should match root version'
|
||||||
|
);
|
||||||
|
assert.equal(rootSendState?.aaaa.status, SendStatus.Delivered, 'aaaa');
|
||||||
|
assert.equal(rootSendState?.bbbb.status, SendStatus.Read, 'bbbb');
|
||||||
|
assert.equal(rootSendState?.cccc.status, SendStatus.Read, 'cccc');
|
||||||
|
assert.equal(rootSendState?.dddd.status, SendStatus.Sent, 'dddd');
|
||||||
|
|
||||||
|
const originalMessageSendState =
|
||||||
|
messageFromDatabase?.editHistory?.[1].sendStateByConversationId;
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
originalMessageSendState?.aaaa.status,
|
||||||
|
SendStatus.Read,
|
||||||
|
'original-aaaa'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
originalMessageSendState?.bbbb.status,
|
||||||
|
SendStatus.Delivered,
|
||||||
|
'original-bbbb'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
originalMessageSendState?.cccc.status,
|
||||||
|
SendStatus.Read,
|
||||||
|
'original-cccc'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
originalMessageSendState?.dddd.status,
|
||||||
|
SendStatus.Sent,
|
||||||
|
'original-dddd'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
Loading…
Add table
Add a link
Reference in a new issue