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
import { webFrame } from 'electron';
import { isNumber, noop } from 'lodash';
import { isNumber } from 'lodash';
import { bindActionCreators } from 'redux';
import { render } from 'react-dom';
import { batch as batchDispatch } from 'react-redux';
@ -13,7 +13,7 @@ import type {
ProcessedDataMessage,
} from './textsecure/Types.d';
import { HTTPError } from './textsecure/Errors';
import {
import createTaskWithTimeout, {
suspendTasksWithTimeout,
resumeTasksWithTimeout,
} from './textsecure/TaskWithTimeout';
@ -34,7 +34,6 @@ import * as durations from './util/durations';
import { explodePromise } from './util/explodePromise';
import { isWindowDragElement } from './util/isWindowDragElement';
import { assert, strictAssert } from './util/assert';
import { dropNull } from './util/dropNull';
import { normalizeUuid } from './util/normalizeUuid';
import { filter } from './util/iterables';
import { isNotNil } from './util/isNotNil';
@ -80,11 +79,11 @@ import type {
SentEventData,
StickerPackEvent,
TypingEvent,
VerifiedEvent,
ViewEvent,
ViewOnceOpenSyncEvent,
ViewSyncEvent,
} from './textsecure/messageReceiverEvents';
import { VerifiedEvent } from './textsecure/messageReceiverEvents';
import type { WebAPIType } from './textsecure/WebAPI';
import * as KeyChangeListener from './textsecure/KeyChangeListener';
import { RotateSignedPreKeyListener } from './textsecure/RotateSignedPreKeyListener';
@ -1709,12 +1708,6 @@ export async function startApp(): Promise<void> {
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.addEventListener('unload', () => notificationService.fastClear());
@ -2082,6 +2075,7 @@ export async function startApp(): Promise<void> {
}
if (firstRun === true && deviceId !== 1) {
const { messaging } = window.textsecure;
const hasThemeSetting = Boolean(window.storage.get('theme-setting'));
if (
!hasThemeSetting &&
@ -2093,19 +2087,71 @@ export async function startApp(): Promise<void> {
);
themeChanged();
}
const syncRequest = window.getSyncRequest();
window.Whisper.events.trigger('contactsync:begin');
syncRequest.addEventListener('success', () => {
log.info('sync successful');
window.storage.put('synced_at', Date.now());
window.Whisper.events.trigger('contactsync');
runStorageService();
});
syncRequest.addEventListener('timeout', () => {
log.error('sync timed out');
window.Whisper.events.trigger('contactsync');
runStorageService();
});
const waitForEvent = createTaskWithTimeout(
(event: string): Promise<void> => {
const { promise, resolve } = explodePromise<void>();
window.Whisper.events.once(event, () => resolve());
return promise;
},
'firstRun:waitForEvent'
);
let storageServiceSyncComplete: Promise<void>;
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();
if (installedStickerPacks.length) {
@ -2122,9 +2168,10 @@ export async function startApp(): Promise<void> {
return;
}
log.info('firstRun: requesting stickers', operations.length);
try {
await singleProtoJobQueue.add(
window.textsecure.messaging.getStickerPackSync(operations)
messaging.getStickerPackSync(operations)
);
} catch (error) {
log.error(
@ -2134,16 +2181,7 @@ export async function startApp(): Promise<void> {
}
}
try {
await singleProtoJobQueue.add(
window.textsecure.messaging.getRequestKeySyncMessage()
);
} catch (error) {
log.error(
'Failed to queue request key sync message',
Errors.toLogFormat(error)
);
}
log.info('firstRun: done');
}
window.storage.onready(async () => {
@ -2486,24 +2524,12 @@ export async function startApp(): Promise<void> {
async function onContactSyncComplete() {
log.info('onContactSyncComplete');
await window.storage.put('synced_at', Date.now());
window.Whisper.events.trigger('contactSync:complete');
}
async function onContactReceived(ev: ContactEvent) {
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({
e164: details.number,
uuid: details.uuid,
@ -2528,19 +2554,6 @@ export async function startApp(): Promise<void> {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
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({
name: details.name,
inbox_position: details.inboxPosition,
@ -2569,6 +2582,8 @@ export async function startApp(): Promise<void> {
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 isValidExpireTimer = typeof expireTimer === 'number';
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()) {
log.info(
'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');
return manifest;
}
@ -1453,6 +1452,9 @@ export const runStorageServiceSyncJob = debounce(() => {
ourProfileKeyService.blockGetWithPromise(
storageJobQueue(async () => {
await sync();
// Notify listeners about sync completion
window.Whisper.events.trigger('storageService:syncComplete');
}, `sync v${window.storage.get('manifestVersion')}`)
);
}, 500);

View File

@ -13,6 +13,7 @@ import {
waitThenRespondToGroupV2Migration,
} from '../groups';
import { assert } from '../util/assert';
import { dropNull } from '../util/dropNull';
import { normalizeUuid } from '../util/normalizeUuid';
import { missingCaseError } from '../util/missingCaseError';
import {
@ -768,8 +769,8 @@ export async function mergeContactRecord(
: undefined,
};
const e164 = contactRecord.serviceE164 || undefined;
const uuid = contactRecord.serviceUuid || undefined;
const e164 = dropNull(contactRecord.serviceE164);
const uuid = dropNull(contactRecord.serviceUuid);
// All contacts must have 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 storageServiceVerified = contactRecord.identityState || 0;
if (verified !== storageServiceVerified) {
@ -1065,6 +1085,16 @@ export async function mergeAccountRecord(
remotelyPinnedConversations.forEach(conversation => {
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);
});

View File

@ -25,6 +25,8 @@ describe('storage service', function needsName() {
it('should handle message request state changes', async () => {
const { phone, desktop, server } = bootstrap;
const initialState = await phone.expectStorageState('initial state');
debug('Creating stranger');
const stranger = await server.createPrimaryDevice({
profileName: 'Mysterious Stranger',
@ -52,9 +54,23 @@ describe('storage service', function needsName() {
)
.click();
const initialState = await phone.expectStorageState('initial state');
assert.strictEqual(initialState.version, 1);
assert.isUndefined(initialState.getContact(stranger));
debug("Verify that we stored stranger's profile key");
const postMessageState = await phone.waitForStorageState({
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');
await conversationStack
@ -64,15 +80,19 @@ describe('storage service', function needsName() {
debug('Verify that storage state was updated');
{
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);
// 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(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
@ -110,6 +130,6 @@ describe('storage service', function needsName() {
debug('Verifying the final manifest version');
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,
contacts: Proto.SyncMessage.IContacts
): Promise<void> {
log.info('contact sync');
log.info('MessageReceiver: handleContacts');
const { blob } = contacts;
if (!blob) {
throw new Error('MessageReceiver.handleContacts: blob field was missing');
@ -2631,11 +2631,11 @@ export default class MessageReceiver
contactDetails = contactBuffer.next();
}
const finalEvent = new ContactSyncEvent();
results.push(this.dispatchAndWait(finalEvent));
await Promise.all(results);
const finalEvent = new ContactSyncEvent();
await this.dispatchAndWait(finalEvent);
log.info('handleContacts: finished');
}