Support for local deletes synced to all your devices

This commit is contained in:
Scott Nonnenberg 2024-05-29 01:56:00 +10:00 committed by GitHub
parent 06f71a7ef8
commit 11eb1782a7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 2094 additions and 72 deletions

View file

@ -130,6 +130,13 @@ import {
StoryRecipientUpdateEvent,
CallLogEventSyncEvent,
CallLinkUpdateSyncEvent,
DeleteForMeSyncEvent,
} from './messageReceiverEvents';
import type {
MessageToDelete,
DeleteForMeSyncEventData,
DeleteForMeSyncTarget,
ConversationToDelete,
} from './messageReceiverEvents';
import * as log from '../logging/log';
import * as durations from '../util/durations';
@ -686,6 +693,11 @@ export default class MessageReceiver
handler: (ev: CallLogEventSyncEvent) => void
): void;
public override addEventListener(
name: 'deleteForMeSync',
handler: (ev: DeleteForMeSyncEvent) => void
): void;
public override addEventListener(name: string, handler: EventHandler): void {
return super.addEventListener(name, handler);
}
@ -3165,6 +3177,9 @@ export default class MessageReceiver
if (syncMessage.callLogEvent) {
return this.handleCallLogEvent(envelope, syncMessage.callLogEvent);
}
if (syncMessage.deleteForMe) {
return this.handleDeleteForMeSync(envelope, syncMessage.deleteForMe);
}
this.removeFromCache(envelope);
const envelopeId = getEnvelopeId(envelope);
@ -3615,6 +3630,118 @@ export default class MessageReceiver
log.info('handleCallLogEvent: finished');
}
private async handleDeleteForMeSync(
envelope: ProcessedEnvelope,
deleteSync: Proto.SyncMessage.IDeleteForMe
): Promise<void> {
const logId = getEnvelopeId(envelope);
log.info('MessageReceiver.handleDeleteForMeSync', logId);
logUnexpectedUrgentValue(envelope, 'deleteForMeSync');
const { timestamp } = envelope;
let eventData: DeleteForMeSyncEventData = [];
try {
if (deleteSync.messageDeletes?.length) {
const messageDeletes: Array<DeleteForMeSyncTarget> =
deleteSync.messageDeletes
.flatMap((item): Array<DeleteForMeSyncTarget> | undefined => {
const messages = item.messages
?.map(message => processMessageToDelete(message, logId))
.filter(isNotNil);
const conversation = item.conversation
? processConversationToDelete(item.conversation, logId)
: undefined;
if (messages?.length && conversation) {
// We want each message in its own task
return messages.map(innerItem => {
return {
type: 'delete-message' as const,
message: innerItem,
conversation,
timestamp,
};
});
}
return undefined;
})
.filter(isNotNil);
eventData = eventData.concat(messageDeletes);
}
if (deleteSync.conversationDeletes?.length) {
const conversationDeletes: Array<DeleteForMeSyncTarget> =
deleteSync.conversationDeletes
.map(item => {
const mostRecentMessages = item.mostRecentMessages
?.map(message => processMessageToDelete(message, logId))
.filter(isNotNil);
const conversation = item.conversation
? processConversationToDelete(item.conversation, logId)
: undefined;
if (mostRecentMessages?.length && conversation) {
return {
type: 'delete-conversation' as const,
conversation,
isFullDelete: Boolean(item.isFullDelete),
mostRecentMessages,
timestamp,
};
}
return undefined;
})
.filter(isNotNil);
eventData = eventData.concat(conversationDeletes);
}
if (deleteSync.localOnlyConversationDeletes?.length) {
const localOnlyConversationDeletes: Array<DeleteForMeSyncTarget> =
deleteSync.localOnlyConversationDeletes
.map(item => {
const conversation = item.conversation
? processConversationToDelete(item.conversation, logId)
: undefined;
if (conversation) {
return {
type: 'delete-local-conversation' as const,
conversation,
timestamp,
};
}
return undefined;
})
.filter(isNotNil);
eventData = eventData.concat(localOnlyConversationDeletes);
}
if (!eventData.length) {
throw new Error(`${logId}: Nothing found in sync message!`);
}
} catch (error: unknown) {
this.removeFromCache(envelope);
throw error;
}
const deleteSyncEventSync = new DeleteForMeSyncEvent(
eventData,
timestamp,
envelope.id,
this.removeFromCache.bind(this, envelope)
);
await this.dispatchAndWait(logId, deleteSyncEventSync);
log.info('handleDeleteForMeSync: finished');
}
private async handleContacts(
envelope: ProcessedEnvelope,
contactSyncProto: Proto.SyncMessage.IContacts
@ -3820,3 +3947,70 @@ function envelopeTypeToCiphertextType(type: number | undefined): number {
throw new Error(`envelopeTypeToCiphertextType: Unknown type ${type}`);
}
function processMessageToDelete(
target: Proto.SyncMessage.DeleteForMe.IAddressableMessage,
logId: string
): MessageToDelete | undefined {
const sentAt = target.sentTimestamp?.toNumber();
if (!isNumber(sentAt)) {
log.warn(
`${logId}/processMessageToDelete: No sentTimestamp found! Dropping AddressableMessage.`
);
return undefined;
}
if (target.authorAci) {
return {
type: 'aci' as const,
authorAci: normalizeAci(
target.authorAci,
`${logId}/processMessageToDelete`
),
sentAt,
};
}
if (target.authorE164) {
return {
type: 'e164' as const,
authorE164: target.authorE164,
sentAt,
};
}
log.warn(
`${logId}/processMessageToDelete: No author field found! Dropping AddressableMessage.`
);
return undefined;
}
function processConversationToDelete(
target: Proto.SyncMessage.DeleteForMe.IConversationIdentifier,
logId: string
): ConversationToDelete | undefined {
const { threadAci, threadGroupId, threadE164 } = target;
if (threadAci) {
return {
type: 'aci' as const,
aci: normalizeAci(threadAci, `${logId}/threadAci`),
};
}
if (threadGroupId) {
return {
type: 'group' as const,
groupId: Buffer.from(threadGroupId).toString('base64'),
};
}
if (threadE164) {
return {
type: 'e164' as const,
e164: threadE164,
};
}
log.warn(
`${logId}/processConversationToDelete: No identifier field found! Dropping ConversationIdentifier.`
);
return undefined;
}