Move receipts and view/read syncs to new syncTasks system
Co-authored-by: Scott Nonnenberg <scott@signal.org>
This commit is contained in:
parent
949104c316
commit
b95dd1a70f
33 changed files with 1242 additions and 612 deletions
463
ts/background.ts
463
ts/background.ts
|
@ -42,7 +42,10 @@ import { isNotNil } from './util/isNotNil';
|
|||
import { isBackupEnabled } from './util/isBackupEnabled';
|
||||
import { setAppLoadingScreenMessage } from './setAppLoadingScreenMessage';
|
||||
import { IdleDetector } from './IdleDetector';
|
||||
import { expiringMessagesDeletionService } from './services/expiringMessagesDeletion';
|
||||
import {
|
||||
initialize as initializeExpiringMessageService,
|
||||
update as updateExpiringMessagesService,
|
||||
} from './services/expiringMessagesDeletion';
|
||||
import { tapToViewMessagesDeletionService } from './services/tapToViewMessagesDeletionService';
|
||||
import { getStoriesForRedux, loadStories } from './services/storyLoader';
|
||||
import {
|
||||
|
@ -116,17 +119,12 @@ import * as Edits from './messageModifiers/Edits';
|
|||
import * as MessageReceipts from './messageModifiers/MessageReceipts';
|
||||
import * as MessageRequests from './messageModifiers/MessageRequests';
|
||||
import * as Reactions from './messageModifiers/Reactions';
|
||||
import * as ReadSyncs from './messageModifiers/ReadSyncs';
|
||||
import * as ViewOnceOpenSyncs from './messageModifiers/ViewOnceOpenSyncs';
|
||||
import * as ViewSyncs from './messageModifiers/ViewSyncs';
|
||||
import type { DeleteAttributesType } from './messageModifiers/Deletes';
|
||||
import type { EditAttributesType } from './messageModifiers/Edits';
|
||||
import type { MessageReceiptAttributesType } from './messageModifiers/MessageReceipts';
|
||||
import type { MessageRequestAttributesType } from './messageModifiers/MessageRequests';
|
||||
import type { ReactionAttributesType } from './messageModifiers/Reactions';
|
||||
import type { ReadSyncAttributesType } from './messageModifiers/ReadSyncs';
|
||||
import type { ViewOnceOpenSyncAttributesType } from './messageModifiers/ViewOnceOpenSyncs';
|
||||
import type { ViewSyncAttributesType } from './messageModifiers/ViewSyncs';
|
||||
import { ReadStatus } from './messages/MessageReadStatus';
|
||||
import type { SendStateByConversationId } from './messages/MessageSendState';
|
||||
import { SendStatus } from './messages/MessageSendState';
|
||||
|
@ -202,7 +200,11 @@ import { getThemeType } from './util/getThemeType';
|
|||
import { AttachmentDownloadManager } from './jobs/AttachmentDownloadManager';
|
||||
import { onCallLinkUpdateSync } from './util/onCallLinkUpdateSync';
|
||||
import { CallMode } from './types/Calling';
|
||||
import type { SyncTaskType } from './util/syncTasks';
|
||||
import { queueSyncTasks } from './util/syncTasks';
|
||||
import type { ViewSyncTaskType } from './messageModifiers/ViewSyncs';
|
||||
import type { ReceiptSyncTaskType } from './messageModifiers/MessageReceipts';
|
||||
import type { ReadSyncTaskType } from './messageModifiers/ReadSyncs';
|
||||
import { isEnabled } from './RemoteConfig';
|
||||
import { AttachmentBackupManager } from './jobs/AttachmentBackupManager';
|
||||
import { getConversationIdForLogging } from './util/idForLogging';
|
||||
|
@ -1498,10 +1500,12 @@ export async function startApp(): Promise<void> {
|
|||
window.Whisper.events.trigger('timetravel');
|
||||
});
|
||||
|
||||
void expiringMessagesDeletionService.update();
|
||||
initializeExpiringMessageService(singleProtoJobQueue);
|
||||
|
||||
void updateExpiringMessagesService();
|
||||
void tapToViewMessagesDeletionService.update();
|
||||
window.Whisper.events.on('timetravel', () => {
|
||||
void expiringMessagesDeletionService.update();
|
||||
void updateExpiringMessagesService();
|
||||
void tapToViewMessagesDeletionService.update();
|
||||
});
|
||||
|
||||
|
@ -1833,7 +1837,9 @@ export async function startApp(): Promise<void> {
|
|||
try {
|
||||
// Note: we always have to register our capabilities all at once, so we do this
|
||||
// after connect on every startup
|
||||
await server.registerCapabilities({});
|
||||
await server.registerCapabilities({
|
||||
deleteSync: true,
|
||||
});
|
||||
} catch (error) {
|
||||
log.error(
|
||||
'Error: Unable to register our capabilities.',
|
||||
|
@ -3221,200 +3227,329 @@ export async function startApp(): Promise<void> {
|
|||
drop(MessageRequests.onResponse(attributes));
|
||||
}
|
||||
|
||||
function onReadReceipt(event: Readonly<ReadEvent>): void {
|
||||
onReadOrViewReceipt({
|
||||
async function onReadReceipt(event: Readonly<ReadEvent>): Promise<void> {
|
||||
return onReadOrViewReceipt({
|
||||
logTitle: 'read receipt',
|
||||
event,
|
||||
type: MessageReceipts.MessageReceiptType.Read,
|
||||
type: MessageReceipts.messageReceiptTypeSchema.enum.Read,
|
||||
});
|
||||
}
|
||||
|
||||
function onViewReceipt(event: Readonly<ViewEvent>): void {
|
||||
onReadOrViewReceipt({
|
||||
async function onViewReceipt(event: Readonly<ViewEvent>): Promise<void> {
|
||||
return onReadOrViewReceipt({
|
||||
logTitle: 'view receipt',
|
||||
event,
|
||||
type: MessageReceipts.MessageReceiptType.View,
|
||||
type: MessageReceipts.messageReceiptTypeSchema.enum.View,
|
||||
});
|
||||
}
|
||||
|
||||
function onReadOrViewReceipt({
|
||||
async function onReadOrViewReceipt({
|
||||
event,
|
||||
logTitle,
|
||||
type,
|
||||
}: Readonly<{
|
||||
event: ReadEvent | ViewEvent;
|
||||
logTitle: string;
|
||||
type:
|
||||
| MessageReceipts.MessageReceiptType.Read
|
||||
| MessageReceipts.MessageReceiptType.View;
|
||||
}>): void {
|
||||
const {
|
||||
envelopeTimestamp,
|
||||
timestamp,
|
||||
source,
|
||||
sourceServiceId,
|
||||
sourceDevice,
|
||||
wasSentEncrypted,
|
||||
} = event.receipt;
|
||||
const sourceConversation = window.ConversationController.lookupOrCreate({
|
||||
serviceId: sourceServiceId,
|
||||
e164: source,
|
||||
reason: `onReadOrViewReceipt(${envelopeTimestamp})`,
|
||||
});
|
||||
strictAssert(sourceConversation, 'Failed to create conversation');
|
||||
log.info(
|
||||
logTitle,
|
||||
`${sourceServiceId || source}.${sourceDevice}`,
|
||||
envelopeTimestamp,
|
||||
'for sent message',
|
||||
timestamp
|
||||
);
|
||||
type: 'Read' | 'View';
|
||||
}>): Promise<void> {
|
||||
const { receipts, envelopeId, envelopeTimestamp, confirm } = event;
|
||||
const logId = `onReadOrViewReceipt(type=${type}, envelope=${envelopeTimestamp}, envelopeId=${envelopeId})`;
|
||||
|
||||
strictAssert(
|
||||
isServiceIdString(sourceServiceId),
|
||||
'onReadOrViewReceipt: Missing sourceServiceId'
|
||||
);
|
||||
strictAssert(sourceDevice, 'onReadOrViewReceipt: Missing sourceDevice');
|
||||
const syncTasks = receipts
|
||||
.map((receipt): SyncTaskType | undefined => {
|
||||
const {
|
||||
timestamp,
|
||||
source,
|
||||
sourceServiceId,
|
||||
sourceDevice,
|
||||
wasSentEncrypted,
|
||||
} = receipt;
|
||||
const sourceConversation = window.ConversationController.lookupOrCreate(
|
||||
{
|
||||
serviceId: sourceServiceId,
|
||||
e164: source,
|
||||
reason: `onReadOrViewReceipt(${envelopeTimestamp})`,
|
||||
}
|
||||
);
|
||||
|
||||
const attributes: MessageReceiptAttributesType = {
|
||||
envelopeId: event.receipt.envelopeId,
|
||||
removeFromMessageReceiverCache: event.confirm,
|
||||
messageSentAt: timestamp,
|
||||
receiptTimestamp: envelopeTimestamp,
|
||||
sourceConversationId: sourceConversation.id,
|
||||
sourceServiceId,
|
||||
sourceDevice,
|
||||
type,
|
||||
wasSentEncrypted,
|
||||
};
|
||||
drop(MessageReceipts.onReceipt(attributes));
|
||||
log.info(
|
||||
logTitle,
|
||||
`${sourceServiceId || source}.${sourceDevice}`,
|
||||
envelopeTimestamp,
|
||||
'for sent message',
|
||||
timestamp
|
||||
);
|
||||
|
||||
if (!sourceConversation) {
|
||||
log.error(`${logId}: Failed to create conversation`);
|
||||
return undefined;
|
||||
}
|
||||
if (!isServiceIdString(sourceServiceId)) {
|
||||
log.error(`${logId}: Missing sourceServiceId`);
|
||||
return undefined;
|
||||
}
|
||||
if (!sourceDevice) {
|
||||
log.error(`${logId}: Missing sourceDevice`);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const data: ReceiptSyncTaskType = {
|
||||
messageSentAt: timestamp,
|
||||
receiptTimestamp: envelopeTimestamp,
|
||||
sourceConversationId: sourceConversation.id,
|
||||
sourceServiceId,
|
||||
sourceDevice,
|
||||
type,
|
||||
wasSentEncrypted,
|
||||
};
|
||||
return {
|
||||
id: generateUuid(),
|
||||
attempts: 1,
|
||||
createdAt: Date.now(),
|
||||
data,
|
||||
envelopeId,
|
||||
sentAt: envelopeTimestamp,
|
||||
type,
|
||||
};
|
||||
})
|
||||
.filter(isNotNil);
|
||||
|
||||
log.info(`${logId}: Saving ${syncTasks.length} sync tasks`);
|
||||
|
||||
await window.Signal.Data.saveSyncTasks(syncTasks);
|
||||
|
||||
confirm();
|
||||
|
||||
log.info(`${logId}: Queuing ${syncTasks.length} sync tasks`);
|
||||
|
||||
await queueSyncTasks(syncTasks, window.Signal.Data.removeSyncTaskById);
|
||||
|
||||
log.info(`${logId}: Done`);
|
||||
}
|
||||
|
||||
async function onReadSync(ev: ReadSyncEvent): Promise<void> {
|
||||
const { envelopeTimestamp, sender, senderAci, timestamp } = ev.read;
|
||||
const readAt = envelopeTimestamp;
|
||||
const { conversation: senderConversation } =
|
||||
window.ConversationController.maybeMergeContacts({
|
||||
aci: senderAci,
|
||||
e164: sender,
|
||||
reason: 'onReadSync',
|
||||
});
|
||||
const senderId = senderConversation?.id;
|
||||
const { reads, envelopeTimestamp, envelopeId, confirm } = ev;
|
||||
const logId = `onReadSync(envelope=${envelopeTimestamp}, envelopeId=${envelopeId})`;
|
||||
|
||||
log.info(
|
||||
'read sync',
|
||||
sender,
|
||||
senderAci,
|
||||
envelopeTimestamp,
|
||||
senderId,
|
||||
'for message',
|
||||
timestamp
|
||||
);
|
||||
const syncTasks = reads
|
||||
.map((read): SyncTaskType | undefined => {
|
||||
const { sender, senderAci, timestamp } = read;
|
||||
const readAt = envelopeTimestamp;
|
||||
const { conversation: senderConversation } =
|
||||
window.ConversationController.maybeMergeContacts({
|
||||
aci: senderAci,
|
||||
e164: sender,
|
||||
reason: 'onReadSync',
|
||||
});
|
||||
const senderId = senderConversation?.id;
|
||||
|
||||
strictAssert(senderId, 'onReadSync missing senderId');
|
||||
strictAssert(senderAci, 'onReadSync missing senderAci');
|
||||
strictAssert(timestamp, 'onReadSync missing timestamp');
|
||||
log.info(
|
||||
'read sync',
|
||||
sender,
|
||||
senderAci,
|
||||
envelopeTimestamp,
|
||||
senderId,
|
||||
'for message',
|
||||
timestamp
|
||||
);
|
||||
|
||||
const attributes: ReadSyncAttributesType = {
|
||||
envelopeId: ev.read.envelopeId,
|
||||
removeFromMessageReceiverCache: ev.confirm,
|
||||
senderId,
|
||||
sender,
|
||||
senderAci,
|
||||
timestamp,
|
||||
readAt,
|
||||
};
|
||||
if (!senderId) {
|
||||
log.error(`${logId}: missing senderId`);
|
||||
return undefined;
|
||||
}
|
||||
if (!senderAci) {
|
||||
log.error(`${logId}: missing senderAci`);
|
||||
return undefined;
|
||||
}
|
||||
if (!timestamp) {
|
||||
log.error(`${logId}: missing timestamp`);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
await ReadSyncs.onSync(attributes);
|
||||
const data: ReadSyncTaskType = {
|
||||
type: 'ReadSync',
|
||||
senderId,
|
||||
sender,
|
||||
senderAci,
|
||||
timestamp,
|
||||
readAt,
|
||||
};
|
||||
return {
|
||||
id: generateUuid(),
|
||||
attempts: 1,
|
||||
createdAt: Date.now(),
|
||||
data,
|
||||
envelopeId,
|
||||
sentAt: envelopeTimestamp,
|
||||
type: 'ReadSync',
|
||||
};
|
||||
})
|
||||
.filter(isNotNil);
|
||||
|
||||
log.info(`${logId}: Saving ${syncTasks.length} sync tasks`);
|
||||
|
||||
await window.Signal.Data.saveSyncTasks(syncTasks);
|
||||
|
||||
confirm();
|
||||
|
||||
log.info(`${logId}: Queuing ${syncTasks.length} sync tasks`);
|
||||
|
||||
await queueSyncTasks(syncTasks, window.Signal.Data.removeSyncTaskById);
|
||||
|
||||
log.info(`${logId}: Done`);
|
||||
}
|
||||
|
||||
async function onViewSync(ev: ViewSyncEvent): Promise<void> {
|
||||
const { envelopeTimestamp, senderE164, senderAci, timestamp } = ev.view;
|
||||
const { conversation: senderConversation } =
|
||||
window.ConversationController.maybeMergeContacts({
|
||||
e164: senderE164,
|
||||
aci: senderAci,
|
||||
reason: 'onViewSync',
|
||||
});
|
||||
const senderId = senderConversation?.id;
|
||||
const { envelopeTimestamp, envelopeId, views, confirm } = ev;
|
||||
const logId = `onViewSync=(envelope=${envelopeTimestamp}, envelopeId=${envelopeId})`;
|
||||
|
||||
log.info(
|
||||
'view sync',
|
||||
senderE164,
|
||||
senderAci,
|
||||
envelopeTimestamp,
|
||||
senderId,
|
||||
'for message',
|
||||
timestamp
|
||||
);
|
||||
const syncTasks = views
|
||||
.map((view): SyncTaskType | undefined => {
|
||||
const { senderAci, senderE164, timestamp } = view;
|
||||
|
||||
strictAssert(senderId, 'onViewSync missing senderId');
|
||||
strictAssert(senderAci, 'onViewSync missing senderAci');
|
||||
strictAssert(timestamp, 'onViewSync missing timestamp');
|
||||
const { conversation: senderConversation } =
|
||||
window.ConversationController.maybeMergeContacts({
|
||||
e164: senderE164,
|
||||
aci: senderAci,
|
||||
reason: 'onViewSync',
|
||||
});
|
||||
const senderId = senderConversation?.id;
|
||||
|
||||
const attributes: ViewSyncAttributesType = {
|
||||
envelopeId: ev.view.envelopeId,
|
||||
removeFromMessageReceiverCache: ev.confirm,
|
||||
senderId,
|
||||
senderE164,
|
||||
senderAci,
|
||||
timestamp,
|
||||
viewedAt: envelopeTimestamp,
|
||||
};
|
||||
log.info(
|
||||
'view sync',
|
||||
senderE164,
|
||||
senderAci,
|
||||
envelopeTimestamp,
|
||||
senderId,
|
||||
'for message',
|
||||
timestamp
|
||||
);
|
||||
|
||||
await ViewSyncs.onSync(attributes);
|
||||
if (!senderId) {
|
||||
log.error(`${logId}: missing senderId`);
|
||||
return undefined;
|
||||
}
|
||||
if (!senderAci) {
|
||||
log.error(`${logId}: missing senderAci`);
|
||||
return undefined;
|
||||
}
|
||||
if (!timestamp) {
|
||||
log.error(`${logId}: missing timestamp`);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const data: ViewSyncTaskType = {
|
||||
type: 'ViewSync',
|
||||
senderId,
|
||||
senderE164,
|
||||
senderAci,
|
||||
timestamp,
|
||||
viewedAt: envelopeTimestamp,
|
||||
};
|
||||
return {
|
||||
id: generateUuid(),
|
||||
attempts: 1,
|
||||
createdAt: Date.now(),
|
||||
data,
|
||||
envelopeId,
|
||||
sentAt: envelopeTimestamp,
|
||||
type: 'ViewSync',
|
||||
};
|
||||
})
|
||||
.filter(isNotNil);
|
||||
|
||||
log.info(`${logId}: Saving ${syncTasks.length} sync tasks`);
|
||||
|
||||
await window.Signal.Data.saveSyncTasks(syncTasks);
|
||||
|
||||
confirm();
|
||||
|
||||
log.info(`${logId}: Queuing ${syncTasks.length} sync tasks`);
|
||||
|
||||
await queueSyncTasks(syncTasks, window.Signal.Data.removeSyncTaskById);
|
||||
|
||||
log.info(`${logId}: Done`);
|
||||
}
|
||||
|
||||
function onDeliveryReceipt(ev: DeliveryEvent): void {
|
||||
const { deliveryReceipt } = ev;
|
||||
const {
|
||||
envelopeTimestamp,
|
||||
sourceServiceId,
|
||||
source,
|
||||
sourceDevice,
|
||||
timestamp,
|
||||
wasSentEncrypted,
|
||||
} = deliveryReceipt;
|
||||
async function onDeliveryReceipt(ev: DeliveryEvent): Promise<void> {
|
||||
const { deliveryReceipts, envelopeId, envelopeTimestamp, confirm } = ev;
|
||||
const logId = `onDeliveryReceipt(envelope=${envelopeTimestamp}, envelopeId=${envelopeId})`;
|
||||
|
||||
const sourceConversation = window.ConversationController.lookupOrCreate({
|
||||
serviceId: sourceServiceId,
|
||||
e164: source,
|
||||
reason: `onDeliveryReceipt(${envelopeTimestamp})`,
|
||||
});
|
||||
strictAssert(envelopeTimestamp, `${logId}: missing envelopeTimestamp`);
|
||||
strictAssert(envelopeTimestamp, `${logId}: missing envelopeId`);
|
||||
|
||||
log.info(
|
||||
'delivery receipt from',
|
||||
`${sourceServiceId || source}.${sourceDevice}`,
|
||||
envelopeTimestamp,
|
||||
'for sent message',
|
||||
timestamp,
|
||||
`wasSentEncrypted=${wasSentEncrypted}`
|
||||
);
|
||||
const syncTasks = deliveryReceipts
|
||||
.map((deliveryReceipt): SyncTaskType | undefined => {
|
||||
const {
|
||||
sourceServiceId,
|
||||
source,
|
||||
sourceDevice,
|
||||
timestamp,
|
||||
wasSentEncrypted,
|
||||
} = deliveryReceipt;
|
||||
|
||||
strictAssert(
|
||||
envelopeTimestamp,
|
||||
'onDeliveryReceipt: missing envelopeTimestamp'
|
||||
);
|
||||
strictAssert(
|
||||
isServiceIdString(sourceServiceId),
|
||||
'onDeliveryReceipt: missing valid sourceServiceId'
|
||||
);
|
||||
strictAssert(sourceDevice, 'onDeliveryReceipt: missing sourceDevice');
|
||||
strictAssert(sourceConversation, 'onDeliveryReceipt: missing conversation');
|
||||
const sourceConversation = window.ConversationController.lookupOrCreate(
|
||||
{
|
||||
serviceId: sourceServiceId,
|
||||
e164: source,
|
||||
reason: `onDeliveryReceipt(${envelopeTimestamp})`,
|
||||
}
|
||||
);
|
||||
|
||||
const attributes: MessageReceiptAttributesType = {
|
||||
envelopeId: ev.deliveryReceipt.envelopeId,
|
||||
removeFromMessageReceiverCache: ev.confirm,
|
||||
messageSentAt: timestamp,
|
||||
receiptTimestamp: envelopeTimestamp,
|
||||
sourceConversationId: sourceConversation.id,
|
||||
sourceServiceId,
|
||||
sourceDevice,
|
||||
type: MessageReceipts.MessageReceiptType.Delivery,
|
||||
wasSentEncrypted,
|
||||
};
|
||||
log.info(
|
||||
'delivery receipt from',
|
||||
`${sourceServiceId || source}.${sourceDevice}`,
|
||||
envelopeTimestamp,
|
||||
'for sent message',
|
||||
timestamp,
|
||||
`wasSentEncrypted=${wasSentEncrypted}`
|
||||
);
|
||||
|
||||
drop(MessageReceipts.onReceipt(attributes));
|
||||
if (!isServiceIdString(sourceServiceId)) {
|
||||
log.error(`${logId}: missing valid sourceServiceId`);
|
||||
return undefined;
|
||||
}
|
||||
if (!sourceDevice) {
|
||||
log.error(`${logId}: missing sourceDevice`);
|
||||
return undefined;
|
||||
}
|
||||
if (!sourceConversation) {
|
||||
log.error(`${logId}: missing conversation`);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const data: ReceiptSyncTaskType = {
|
||||
messageSentAt: timestamp,
|
||||
receiptTimestamp: envelopeTimestamp,
|
||||
sourceConversationId: sourceConversation.id,
|
||||
sourceServiceId,
|
||||
sourceDevice,
|
||||
type: MessageReceipts.messageReceiptTypeSchema.enum.Delivery,
|
||||
wasSentEncrypted,
|
||||
};
|
||||
return {
|
||||
id: generateUuid(),
|
||||
attempts: 1,
|
||||
createdAt: Date.now(),
|
||||
data,
|
||||
envelopeId,
|
||||
sentAt: envelopeTimestamp,
|
||||
type: 'Delivery',
|
||||
};
|
||||
})
|
||||
.filter(isNotNil);
|
||||
|
||||
log.info(`${logId}: Saving ${syncTasks.length} sync tasks`);
|
||||
|
||||
await window.Signal.Data.saveSyncTasks(syncTasks);
|
||||
|
||||
confirm();
|
||||
|
||||
log.info(`${logId}: Queuing ${syncTasks.length} sync tasks`);
|
||||
|
||||
await queueSyncTasks(syncTasks, window.Signal.Data.removeSyncTaskById);
|
||||
|
||||
log.info(`${logId}: Done`);
|
||||
}
|
||||
|
||||
async function onDeleteForMeSync(ev: DeleteForMeSyncEvent) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue