contactSync should depend on syncMessage.complete

This commit is contained in:
Fedor Indutny 2022-08-26 15:26:38 -07:00 committed by GitHub
parent 299044f89f
commit c42df6312e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 46 additions and 29 deletions

View file

@ -13,6 +13,7 @@ import type { ConversationModel } from '../models/conversations';
import { validateConversation } from '../util/validateConversation'; import { validateConversation } from '../util/validateConversation';
import { strictAssert } from '../util/assert'; import { strictAssert } from '../util/assert';
import { isDirectConversation, isMe } from '../util/whatTypeOfConversation'; import { isDirectConversation, isMe } from '../util/whatTypeOfConversation';
import { normalizeUuid } from '../util/normalizeUuid';
import * as log from '../logging/log'; import * as log from '../logging/log';
// When true - we are running the very first storage and contact sync after // When true - we are running the very first storage and contact sync after
@ -78,11 +79,24 @@ const queue = new PQueue({ concurrency: 1 });
async function doContactSync({ async function doContactSync({
contacts, contacts,
complete,
receivedAtCounter, receivedAtCounter,
}: ContactSyncEvent): Promise<void> { }: ContactSyncEvent): Promise<void> {
log.info( // iOS sets `syncMessage.contacts.complete` flag to `true` unconditionally
`doContactSync(${receivedAtCounter}): got ${contacts.length} contacts` // and so we have to employ tricks to figure out whether the sync is full or
); // partial. Thankfully, iOS sends only two kinds of contact syncs: full or
// local sync. Local sync is always a single our own contact so we can do an
// UUID check.
const isFullSync =
complete &&
!(
contacts.length === 1 &&
normalizeUuid(contacts[0].uuid, 'doContactSync') ===
window.storage.user.getUuid()?.toString()
);
const logId = `doContactSync(${receivedAtCounter}, isFullSync=${isFullSync})`;
log.info(`${logId}: got ${contacts.length} contacts`);
const updatedConversations = new Set<ConversationModel>(); const updatedConversations = new Set<ConversationModel>();
@ -97,7 +111,7 @@ async function doContactSync({
const validationError = validateConversation(partialConversation); const validationError = validateConversation(partialConversation);
if (validationError) { if (validationError) {
log.error( log.error(
`doContactSync(${receivedAtCounter}): Invalid contact received`, `${logId}: Invalid contact received`,
Errors.toLogFormat(validationError) Errors.toLogFormat(validationError)
); );
continue; continue;
@ -106,32 +120,29 @@ async function doContactSync({
const conversation = window.ConversationController.maybeMergeContacts({ const conversation = window.ConversationController.maybeMergeContacts({
e164: details.number, e164: details.number,
aci: details.uuid, aci: details.uuid,
reason: `doContactSync(${receivedAtCounter})`, reason: logId,
}); });
strictAssert(conversation, 'need conversation to queue the job!'); strictAssert(conversation, 'need conversation to queue the job!');
// It's important to use queueJob here because we might update the expiration timer // It's important to use queueJob here because we might update the expiration timer
// and we don't want conflicts with incoming message processing happening on the // and we don't want conflicts with incoming message processing happening on the
// conversation queue. // conversation queue.
const job = conversation.queueJob( const job = conversation.queueJob(`${logId}.set`, async () => {
`doContactSync(${receivedAtCounter}).set`, try {
async () => { await updateConversationFromContactSync(
try { conversation,
await updateConversationFromContactSync( details,
conversation, receivedAtCounter
details, );
receivedAtCounter
);
updatedConversations.add(conversation); updatedConversations.add(conversation);
} catch (error) { } catch (error) {
log.error( log.error(
'updateConversationFromContactSync error:', 'updateConversationFromContactSync error:',
Errors.toLogFormat(error) Errors.toLogFormat(error)
); );
}
} }
); });
promises.push(job); promises.push(job);
} }
@ -140,15 +151,19 @@ async function doContactSync({
await Promise.all(promises); await Promise.all(promises);
promises = []; promises = [];
const notUpdated = window.ConversationController.getAll().filter( // Erase data in conversations that are not the part of contact sync only
convo => // if we received a full contact sync (and not a one-off contact update).
!updatedConversations.has(convo) && const notUpdated = isFullSync
isDirectConversation(convo.attributes) && ? window.ConversationController.getAll().filter(
!isMe(convo.attributes) convo =>
); !updatedConversations.has(convo) &&
isDirectConversation(convo.attributes) &&
!isMe(convo.attributes)
)
: [];
log.info( log.info(
`doContactSync(${receivedAtCounter}): ` + `${logId}: ` +
`updated ${updatedConversations.size} ` + `updated ${updatedConversations.size} ` +
`resetting ${notUpdated.length}` `resetting ${notUpdated.length}`
); );

View file

@ -3070,6 +3070,7 @@ export default class MessageReceiver
const contactSync = new ContactSyncEvent( const contactSync = new ContactSyncEvent(
Array.from(contactBuffer), Array.from(contactBuffer),
Boolean(contacts.complete),
envelope.receivedAtCounter envelope.receivedAtCounter
); );
await this.dispatchAndWait(contactSync); await this.dispatchAndWait(contactSync);

View file

@ -76,6 +76,7 @@ export class ErrorEvent extends Event {
export class ContactSyncEvent extends Event { export class ContactSyncEvent extends Event {
constructor( constructor(
public readonly contacts: ReadonlyArray<ModifiedContactDetails>, public readonly contacts: ReadonlyArray<ModifiedContactDetails>,
public readonly complete: boolean,
public readonly receivedAtCounter: number public readonly receivedAtCounter: number
) { ) {
super('contactSync'); super('contactSync');