2023-12-18 20:27:18 +00:00
|
|
|
// Copyright 2023 Signal Messenger, LLC
|
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
2024-10-08 03:17:03 +00:00
|
|
|
import { v4 as uuid } from 'uuid';
|
2023-12-18 20:27:18 +00:00
|
|
|
import { assert } from 'chai';
|
|
|
|
|
|
|
|
import { type AciString, generateAci } from '../types/ServiceId';
|
|
|
|
import type { MessageAttributesType } from '../model-types';
|
2024-07-22 18:16:33 +00:00
|
|
|
import { DataReader, DataWriter } from '../sql/Client';
|
2023-12-18 20:27:18 +00:00
|
|
|
import { SendStatus } from '../messages/MessageSendState';
|
2024-06-17 19:24:39 +00:00
|
|
|
import type {
|
|
|
|
MessageReceiptAttributesType,
|
2023-12-18 20:27:18 +00:00
|
|
|
MessageReceiptType,
|
2024-06-17 19:24:39 +00:00
|
|
|
} from '../messageModifiers/MessageReceipts';
|
|
|
|
import {
|
2023-12-18 20:27:18 +00:00
|
|
|
onReceipt,
|
2024-06-17 19:24:39 +00:00
|
|
|
messageReceiptTypeSchema,
|
2023-12-18 20:27:18 +00:00
|
|
|
} 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(),
|
2024-06-17 19:24:39 +00:00
|
|
|
syncTaskId: uuid(),
|
|
|
|
receiptSync: {
|
|
|
|
messageSentAt,
|
|
|
|
receiptTimestamp: 1,
|
|
|
|
sourceConversationId,
|
|
|
|
sourceDevice: 1,
|
|
|
|
sourceServiceId: generateAci(),
|
|
|
|
type,
|
|
|
|
wasSentEncrypted: true,
|
|
|
|
},
|
2023-12-18 20:27:18 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
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(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2024-07-22 18:16:33 +00:00
|
|
|
await DataWriter.saveMessage(messageAttributes, {
|
2023-12-18 20:27:18 +00:00
|
|
|
forceSave: true,
|
|
|
|
ourAci,
|
|
|
|
});
|
|
|
|
|
|
|
|
await Promise.all([
|
2024-06-17 19:24:39 +00:00
|
|
|
onReceipt(
|
|
|
|
generateReceipt('aaaa', sentAt, messageReceiptTypeSchema.enum.Delivery)
|
|
|
|
),
|
|
|
|
onReceipt(
|
|
|
|
generateReceipt('bbbb', sentAt, messageReceiptTypeSchema.enum.Delivery)
|
|
|
|
),
|
|
|
|
onReceipt(
|
|
|
|
generateReceipt('cccc', sentAt, messageReceiptTypeSchema.enum.Read)
|
|
|
|
),
|
|
|
|
onReceipt(
|
|
|
|
generateReceipt('aaaa', sentAt, messageReceiptTypeSchema.enum.Read)
|
|
|
|
),
|
2023-12-18 20:27:18 +00:00
|
|
|
]);
|
|
|
|
|
2024-07-22 18:16:33 +00:00
|
|
|
const messageFromDatabase = await DataReader.getMessageById(id);
|
2023-12-18 20:27:18 +00:00
|
|
|
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,
|
2024-03-25 19:21:14 +00:00
|
|
|
received_at: 2,
|
|
|
|
received_at_ms: Date.now(),
|
2023-12-18 20:27:18 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
sendStateByConversationId: defaultSendState,
|
|
|
|
timestamp: sentAt,
|
2024-03-25 19:21:14 +00:00
|
|
|
received_at: 1,
|
|
|
|
received_at_ms: Date.now(),
|
2023-12-18 20:27:18 +00:00
|
|
|
},
|
|
|
|
],
|
|
|
|
};
|
|
|
|
|
2024-07-22 18:16:33 +00:00
|
|
|
await DataWriter.saveMessage(messageAttributes, {
|
2023-12-18 20:27:18 +00:00
|
|
|
forceSave: true,
|
|
|
|
ourAci,
|
|
|
|
});
|
2024-07-22 18:16:33 +00:00
|
|
|
await DataWriter.saveEditedMessage(messageAttributes, ourAci, {
|
2023-12-18 20:27:18 +00:00
|
|
|
conversationId: messageAttributes.conversationId,
|
|
|
|
messageId: messageAttributes.id,
|
|
|
|
readStatus: ReadStatus.Read,
|
|
|
|
sentAt: editedSentAt,
|
|
|
|
});
|
|
|
|
|
|
|
|
await Promise.all([
|
|
|
|
// send receipts for original message
|
2024-06-17 19:24:39 +00:00
|
|
|
onReceipt(
|
|
|
|
generateReceipt('aaaa', sentAt, messageReceiptTypeSchema.enum.Delivery)
|
|
|
|
),
|
|
|
|
onReceipt(
|
|
|
|
generateReceipt('bbbb', sentAt, messageReceiptTypeSchema.enum.Delivery)
|
|
|
|
),
|
|
|
|
onReceipt(
|
|
|
|
generateReceipt('cccc', sentAt, messageReceiptTypeSchema.enum.Read)
|
|
|
|
),
|
|
|
|
onReceipt(
|
|
|
|
generateReceipt('aaaa', sentAt, messageReceiptTypeSchema.enum.Read)
|
|
|
|
),
|
2023-12-18 20:27:18 +00:00
|
|
|
|
|
|
|
// and send receipts for edited message
|
|
|
|
onReceipt(
|
2024-06-17 19:24:39 +00:00
|
|
|
generateReceipt(
|
|
|
|
'aaaa',
|
|
|
|
editedSentAt,
|
|
|
|
messageReceiptTypeSchema.enum.Delivery
|
|
|
|
)
|
|
|
|
),
|
|
|
|
onReceipt(
|
|
|
|
generateReceipt(
|
|
|
|
'bbbb',
|
|
|
|
editedSentAt,
|
|
|
|
messageReceiptTypeSchema.enum.Delivery
|
|
|
|
)
|
|
|
|
),
|
|
|
|
onReceipt(
|
|
|
|
generateReceipt(
|
|
|
|
'cccc',
|
|
|
|
editedSentAt,
|
|
|
|
messageReceiptTypeSchema.enum.Read
|
|
|
|
)
|
2023-12-18 20:27:18 +00:00
|
|
|
),
|
|
|
|
onReceipt(
|
2024-06-17 19:24:39 +00:00
|
|
|
generateReceipt(
|
|
|
|
'bbbb',
|
|
|
|
editedSentAt,
|
|
|
|
messageReceiptTypeSchema.enum.Read
|
|
|
|
)
|
2023-12-18 20:27:18 +00:00
|
|
|
),
|
|
|
|
]);
|
|
|
|
|
2024-07-22 18:16:33 +00:00
|
|
|
const messageFromDatabase = await DataReader.getMessageById(id);
|
2023-12-18 20:27:18 +00:00
|
|
|
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'
|
|
|
|
);
|
|
|
|
});
|
|
|
|
});
|