Change order of syncs during linking

This commit is contained in:
Fedor Indutny 2022-03-02 14:53:47 -08:00 committed by GitHub
parent 4f869e7900
commit 3b4106d9dd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 142 additions and 90 deletions

View file

@ -2,7 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import { webFrame } from 'electron'; import { webFrame } from 'electron';
import { isNumber, noop } from 'lodash'; import { isNumber } from 'lodash';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { render } from 'react-dom'; import { render } from 'react-dom';
import { batch as batchDispatch } from 'react-redux'; import { batch as batchDispatch } from 'react-redux';
@ -13,7 +13,7 @@ import type {
ProcessedDataMessage, ProcessedDataMessage,
} from './textsecure/Types.d'; } from './textsecure/Types.d';
import { HTTPError } from './textsecure/Errors'; import { HTTPError } from './textsecure/Errors';
import { import createTaskWithTimeout, {
suspendTasksWithTimeout, suspendTasksWithTimeout,
resumeTasksWithTimeout, resumeTasksWithTimeout,
} from './textsecure/TaskWithTimeout'; } from './textsecure/TaskWithTimeout';
@ -34,7 +34,6 @@ import * as durations from './util/durations';
import { explodePromise } from './util/explodePromise'; import { explodePromise } from './util/explodePromise';
import { isWindowDragElement } from './util/isWindowDragElement'; import { isWindowDragElement } from './util/isWindowDragElement';
import { assert, strictAssert } from './util/assert'; import { assert, strictAssert } from './util/assert';
import { dropNull } from './util/dropNull';
import { normalizeUuid } from './util/normalizeUuid'; import { normalizeUuid } from './util/normalizeUuid';
import { filter } from './util/iterables'; import { filter } from './util/iterables';
import { isNotNil } from './util/isNotNil'; import { isNotNil } from './util/isNotNil';
@ -80,11 +79,11 @@ import type {
SentEventData, SentEventData,
StickerPackEvent, StickerPackEvent,
TypingEvent, TypingEvent,
VerifiedEvent,
ViewEvent, ViewEvent,
ViewOnceOpenSyncEvent, ViewOnceOpenSyncEvent,
ViewSyncEvent, ViewSyncEvent,
} from './textsecure/messageReceiverEvents'; } from './textsecure/messageReceiverEvents';
import { VerifiedEvent } from './textsecure/messageReceiverEvents';
import type { WebAPIType } from './textsecure/WebAPI'; import type { WebAPIType } from './textsecure/WebAPI';
import * as KeyChangeListener from './textsecure/KeyChangeListener'; import * as KeyChangeListener from './textsecure/KeyChangeListener';
import { RotateSignedPreKeyListener } from './textsecure/RotateSignedPreKeyListener'; import { RotateSignedPreKeyListener } from './textsecure/RotateSignedPreKeyListener';
@ -1709,12 +1708,6 @@ export async function startApp(): Promise<void> {
window.reduxActions.app.openInstaller(); window.reduxActions.app.openInstaller();
} }
window.Whisper.events.on('contactsync', () => {
if (window.reduxStore.getState().app.appView === AppViewType.Installer) {
window.reduxActions.app.openInbox();
}
});
window.registerForActive(() => notificationService.clear()); window.registerForActive(() => notificationService.clear());
window.addEventListener('unload', () => notificationService.fastClear()); window.addEventListener('unload', () => notificationService.fastClear());
@ -2082,6 +2075,7 @@ export async function startApp(): Promise<void> {
} }
if (firstRun === true && deviceId !== 1) { if (firstRun === true && deviceId !== 1) {
const { messaging } = window.textsecure;
const hasThemeSetting = Boolean(window.storage.get('theme-setting')); const hasThemeSetting = Boolean(window.storage.get('theme-setting'));
if ( if (
!hasThemeSetting && !hasThemeSetting &&
@ -2093,19 +2087,71 @@ export async function startApp(): Promise<void> {
); );
themeChanged(); themeChanged();
} }
const syncRequest = window.getSyncRequest();
window.Whisper.events.trigger('contactsync:begin'); const waitForEvent = createTaskWithTimeout(
syncRequest.addEventListener('success', () => { (event: string): Promise<void> => {
log.info('sync successful'); const { promise, resolve } = explodePromise<void>();
window.storage.put('synced_at', Date.now()); window.Whisper.events.once(event, () => resolve());
window.Whisper.events.trigger('contactsync'); return promise;
runStorageService(); },
}); 'firstRun:waitForEvent'
syncRequest.addEventListener('timeout', () => { );
log.error('sync timed out');
window.Whisper.events.trigger('contactsync'); let storageServiceSyncComplete: Promise<void>;
runStorageService(); if (window.ConversationController.areWePrimaryDevice()) {
}); storageServiceSyncComplete = Promise.resolve();
} else {
storageServiceSyncComplete = waitForEvent(
'storageService:syncComplete'
);
}
const contactSyncComplete = waitForEvent('contactSync:complete');
log.info('firstRun: requesting initial sync');
// Request configuration, block, GV1 sync messages, contacts
// (only avatars and inboxPosition),and Storage Service sync.
try {
await Promise.all([
singleProtoJobQueue.add(
messaging.getRequestConfigurationSyncMessage()
),
singleProtoJobQueue.add(messaging.getRequestBlockSyncMessage()),
singleProtoJobQueue.add(messaging.getRequestGroupSyncMessage()),
singleProtoJobQueue.add(messaging.getRequestContactSyncMessage()),
runStorageService(),
]);
} catch (error) {
log.error(
'connect: Failed to request initial syncs',
Errors.toLogFormat(error)
);
}
log.info('firstRun: waiting for storage service and contact sync');
try {
await Promise.all([storageServiceSyncComplete, contactSyncComplete]);
} catch (error) {
log.error(
'connect: Failed to run storage service and contact syncs',
Errors.toLogFormat(error)
);
}
log.info('firstRun: disabling post link experience');
window.Signal.Util.postLinkExperience.stop();
// Switch to inbox view even if contact sync is still running
if (
window.reduxStore.getState().app.appView === AppViewType.Installer
) {
log.info('firstRun: opening inbox');
window.reduxActions.app.openInbox();
} else {
log.info('firstRun: not opening inbox');
}
const installedStickerPacks = Stickers.getInstalledStickerPacks(); const installedStickerPacks = Stickers.getInstalledStickerPacks();
if (installedStickerPacks.length) { if (installedStickerPacks.length) {
@ -2122,9 +2168,10 @@ export async function startApp(): Promise<void> {
return; return;
} }
log.info('firstRun: requesting stickers', operations.length);
try { try {
await singleProtoJobQueue.add( await singleProtoJobQueue.add(
window.textsecure.messaging.getStickerPackSync(operations) messaging.getStickerPackSync(operations)
); );
} catch (error) { } catch (error) {
log.error( log.error(
@ -2134,16 +2181,7 @@ export async function startApp(): Promise<void> {
} }
} }
try { log.info('firstRun: done');
await singleProtoJobQueue.add(
window.textsecure.messaging.getRequestKeySyncMessage()
);
} catch (error) {
log.error(
'Failed to queue request key sync message',
Errors.toLogFormat(error)
);
}
} }
window.storage.onready(async () => { window.storage.onready(async () => {
@ -2486,24 +2524,12 @@ export async function startApp(): Promise<void> {
async function onContactSyncComplete() { async function onContactSyncComplete() {
log.info('onContactSyncComplete'); log.info('onContactSyncComplete');
await window.storage.put('synced_at', Date.now()); await window.storage.put('synced_at', Date.now());
window.Whisper.events.trigger('contactSync:complete');
} }
async function onContactReceived(ev: ContactEvent) { async function onContactReceived(ev: ContactEvent) {
const details = ev.contactDetails; const details = ev.contactDetails;
if (
(details.number &&
details.number === window.textsecure.storage.user.getNumber()) ||
(details.uuid &&
details.uuid === window.textsecure.storage.user.getUuid()?.toString())
) {
// special case for syncing details about ourselves
if (details.profileKey) {
log.info('Got sync message with our own profile key');
ourProfileKeyService.set(details.profileKey);
}
}
const c = new window.Whisper.Conversation({ const c = new window.Whisper.Conversation({
e164: details.number, e164: details.number,
uuid: details.uuid, uuid: details.uuid,
@ -2528,19 +2554,6 @@ export async function startApp(): Promise<void> {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const conversation = window.ConversationController.get(detailsId)!; const conversation = window.ConversationController.get(detailsId)!;
if (details.profileKey && details.profileKey.length > 0) {
const profileKey = Bytes.toBase64(details.profileKey);
conversation.setProfileKey(profileKey);
}
if (typeof details.blocked !== 'undefined') {
if (details.blocked) {
conversation.block();
} else {
conversation.unblock();
}
}
conversation.set({ conversation.set({
name: details.name, name: details.name,
inbox_position: details.inboxPosition, inbox_position: details.inboxPosition,
@ -2569,6 +2582,8 @@ export async function startApp(): Promise<void> {
window.Signal.Data.updateConversation(conversation.attributes); window.Signal.Data.updateConversation(conversation.attributes);
// expireTimer isn't stored in Storage Service so we have to rely on the
// contact sync.
const { expireTimer } = details; const { expireTimer } = details;
const isValidExpireTimer = typeof expireTimer === 'number'; const isValidExpireTimer = typeof expireTimer === 'number';
if (isValidExpireTimer) { if (isValidExpireTimer) {
@ -2585,21 +2600,6 @@ export async function startApp(): Promise<void> {
); );
} }
if (details.verified) {
const { verified } = details;
const verifiedEvent = new VerifiedEvent(
{
state: dropNull(verified.state),
destination: dropNull(verified.destination),
destinationUuid: dropNull(verified.destinationUuid),
identityKey: dropNull(verified.identityKey),
viaContactSync: true,
},
noop
);
await onVerified(verifiedEvent);
}
if (window.Signal.Util.postLinkExperience.isActive()) { if (window.Signal.Util.postLinkExperience.isActive()) {
log.info( log.info(
'onContactReceived: Adding the message history disclaimer on link' 'onContactReceived: Adding the message history disclaimer on link'

View file

@ -1312,7 +1312,6 @@ async function sync(
); );
} }
window.Signal.Util.postLinkExperience.stop();
log.info('storageService.sync: complete'); log.info('storageService.sync: complete');
return manifest; return manifest;
} }
@ -1453,6 +1452,9 @@ export const runStorageServiceSyncJob = debounce(() => {
ourProfileKeyService.blockGetWithPromise( ourProfileKeyService.blockGetWithPromise(
storageJobQueue(async () => { storageJobQueue(async () => {
await sync(); await sync();
// Notify listeners about sync completion
window.Whisper.events.trigger('storageService:syncComplete');
}, `sync v${window.storage.get('manifestVersion')}`) }, `sync v${window.storage.get('manifestVersion')}`)
); );
}, 500); }, 500);

View file

@ -13,6 +13,7 @@ import {
waitThenRespondToGroupV2Migration, waitThenRespondToGroupV2Migration,
} from '../groups'; } from '../groups';
import { assert } from '../util/assert'; import { assert } from '../util/assert';
import { dropNull } from '../util/dropNull';
import { normalizeUuid } from '../util/normalizeUuid'; import { normalizeUuid } from '../util/normalizeUuid';
import { missingCaseError } from '../util/missingCaseError'; import { missingCaseError } from '../util/missingCaseError';
import { import {
@ -768,8 +769,8 @@ export async function mergeContactRecord(
: undefined, : undefined,
}; };
const e164 = contactRecord.serviceE164 || undefined; const e164 = dropNull(contactRecord.serviceE164);
const uuid = contactRecord.serviceUuid || undefined; const uuid = dropNull(contactRecord.serviceUuid);
// All contacts must have UUID // All contacts must have UUID
if (!uuid) { if (!uuid) {
@ -802,6 +803,25 @@ export async function mergeContactRecord(
}); });
} }
const remoteName = dropNull(contactRecord.givenName);
const remoteFamilyName = dropNull(contactRecord.familyName);
const localName = conversation.get('profileName');
const localFamilyName = conversation.get('profileFamilyName');
if (
remoteName &&
(localName !== remoteName || localFamilyName !== remoteFamilyName)
) {
// Local name doesn't match remote name, fetch profile
if (localName) {
conversation.getProfiles();
} else {
conversation.set({
profileName: remoteName,
profileFamilyName: remoteFamilyName,
});
}
}
const verified = await conversation.safeGetVerified(); const verified = await conversation.safeGetVerified();
const storageServiceVerified = contactRecord.identityState || 0; const storageServiceVerified = contactRecord.identityState || 0;
if (verified !== storageServiceVerified) { if (verified !== storageServiceVerified) {
@ -1065,6 +1085,16 @@ export async function mergeAccountRecord(
remotelyPinnedConversations.forEach(conversation => { remotelyPinnedConversations.forEach(conversation => {
conversation.set({ isPinned: true, isArchived: false }); conversation.set({ isPinned: true, isArchived: false });
if (
window.Signal.Util.postLinkExperience.isActive() &&
isGroupV2(conversation.attributes)
) {
log.info(
'mergeAccountRecord: Adding the message history disclaimer on link'
);
conversation.addMessageHistoryDisclaimer();
}
updateConversation(conversation.attributes); updateConversation(conversation.attributes);
}); });

View file

@ -25,6 +25,8 @@ describe('storage service', function needsName() {
it('should handle message request state changes', async () => { it('should handle message request state changes', async () => {
const { phone, desktop, server } = bootstrap; const { phone, desktop, server } = bootstrap;
const initialState = await phone.expectStorageState('initial state');
debug('Creating stranger'); debug('Creating stranger');
const stranger = await server.createPrimaryDevice({ const stranger = await server.createPrimaryDevice({
profileName: 'Mysterious Stranger', profileName: 'Mysterious Stranger',
@ -52,9 +54,23 @@ describe('storage service', function needsName() {
) )
.click(); .click();
const initialState = await phone.expectStorageState('initial state'); debug("Verify that we stored stranger's profile key");
assert.strictEqual(initialState.version, 1); const postMessageState = await phone.waitForStorageState({
assert.isUndefined(initialState.getContact(stranger)); after: initialState,
});
{
assert.strictEqual(postMessageState.version, 2);
assert.isFalse(postMessageState.getContact(stranger)?.whitelisted);
assert.strictEqual(
postMessageState.getContact(stranger)?.profileKey?.length,
32
);
// ContactRecord
const { added, removed } = postMessageState.diff(initialState);
assert.strictEqual(added.length, 1, 'only one record must be added');
assert.strictEqual(removed.length, 0, 'no records should be removed');
}
debug('Accept conversation from a stranger'); debug('Accept conversation from a stranger');
await conversationStack await conversationStack
@ -64,15 +80,19 @@ describe('storage service', function needsName() {
debug('Verify that storage state was updated'); debug('Verify that storage state was updated');
{ {
const nextState = await phone.waitForStorageState({ const nextState = await phone.waitForStorageState({
after: initialState, after: postMessageState,
}); });
assert.strictEqual(nextState.version, 2); assert.strictEqual(nextState.version, 3);
assert.isTrue(nextState.getContact(stranger)?.whitelisted); assert.isTrue(nextState.getContact(stranger)?.whitelisted);
// ContactRecord // ContactRecord
const { added, removed } = nextState.diff(initialState); const { added, removed } = nextState.diff(postMessageState);
assert.strictEqual(added.length, 1, 'only one record must be added'); assert.strictEqual(added.length, 1, 'only one record must be added');
assert.strictEqual(removed.length, 0, 'no records should be removed'); assert.strictEqual(
removed.length,
1,
'only one record should be removed'
);
} }
// Stranger should receive our profile key // Stranger should receive our profile key
@ -110,6 +130,6 @@ describe('storage service', function needsName() {
debug('Verifying the final manifest version'); debug('Verifying the final manifest version');
const finalState = await phone.expectStorageState('consistency check'); const finalState = await phone.expectStorageState('consistency check');
assert.strictEqual(finalState.version, 2); assert.strictEqual(finalState.version, 3);
}); });
}); });

View file

@ -2610,7 +2610,7 @@ export default class MessageReceiver
envelope: ProcessedEnvelope, envelope: ProcessedEnvelope,
contacts: Proto.SyncMessage.IContacts contacts: Proto.SyncMessage.IContacts
): Promise<void> { ): Promise<void> {
log.info('contact sync'); log.info('MessageReceiver: handleContacts');
const { blob } = contacts; const { blob } = contacts;
if (!blob) { if (!blob) {
throw new Error('MessageReceiver.handleContacts: blob field was missing'); throw new Error('MessageReceiver.handleContacts: blob field was missing');
@ -2631,11 +2631,11 @@ export default class MessageReceiver
contactDetails = contactBuffer.next(); contactDetails = contactBuffer.next();
} }
const finalEvent = new ContactSyncEvent();
results.push(this.dispatchAndWait(finalEvent));
await Promise.all(results); await Promise.all(results);
const finalEvent = new ContactSyncEvent();
await this.dispatchAndWait(finalEvent);
log.info('handleContacts: finished'); log.info('handleContacts: finished');
} }