Don't block message processing on contact syncs
This commit is contained in:
parent
e5d03edea1
commit
c11a894ad1
5 changed files with 59 additions and 59 deletions
|
@ -542,7 +542,6 @@ export async function startApp(): Promise<void> {
|
||||||
|
|
||||||
log.info('Initializing MessageReceiver');
|
log.info('Initializing MessageReceiver');
|
||||||
messageReceiver = new MessageReceiver({
|
messageReceiver = new MessageReceiver({
|
||||||
server,
|
|
||||||
storage: window.storage,
|
storage: window.storage,
|
||||||
serverTrustRoot: window.getServerTrustRoot(),
|
serverTrustRoot: window.getServerTrustRoot(),
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,7 +5,10 @@ import PQueue from 'p-queue';
|
||||||
|
|
||||||
import { DataWriter } from '../sql/Client';
|
import { DataWriter } from '../sql/Client';
|
||||||
import type { ContactSyncEvent } from '../textsecure/messageReceiverEvents';
|
import type { ContactSyncEvent } from '../textsecure/messageReceiverEvents';
|
||||||
import type { ContactDetailsWithAvatar } from '../textsecure/ContactsParser';
|
import {
|
||||||
|
parseContactsV2,
|
||||||
|
type ContactDetailsWithAvatar,
|
||||||
|
} from '../textsecure/ContactsParser';
|
||||||
import { normalizeAci } from '../util/normalizeAci';
|
import { normalizeAci } from '../util/normalizeAci';
|
||||||
import * as Conversation from '../types/Conversation';
|
import * as Conversation from '../types/Conversation';
|
||||||
import * as Errors from '../types/errors';
|
import * as Errors from '../types/errors';
|
||||||
|
@ -15,6 +18,11 @@ import { validateConversation } from '../util/validateConversation';
|
||||||
import { isDirectConversation, isMe } from '../util/whatTypeOfConversation';
|
import { isDirectConversation, isMe } from '../util/whatTypeOfConversation';
|
||||||
import * as log from '../logging/log';
|
import * as log from '../logging/log';
|
||||||
import { dropNull } from '../util/dropNull';
|
import { dropNull } from '../util/dropNull';
|
||||||
|
import type { ProcessedAttachment } from '../textsecure/Types';
|
||||||
|
import { downloadAttachment } from '../textsecure/downloadAttachment';
|
||||||
|
import { strictAssert } from '../util/assert';
|
||||||
|
import type { ReencryptedAttachmentV2 } from '../AttachmentCrypto';
|
||||||
|
import { SECOND } from '../util/durations';
|
||||||
|
|
||||||
// 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
|
||||||
// linking.
|
// linking.
|
||||||
|
@ -86,8 +94,34 @@ async function updateConversationFromContactSync(
|
||||||
|
|
||||||
const queue = new PQueue({ concurrency: 1 });
|
const queue = new PQueue({ concurrency: 1 });
|
||||||
|
|
||||||
|
async function downloadAndParseContactAttachment(
|
||||||
|
contactAttachment: ProcessedAttachment
|
||||||
|
) {
|
||||||
|
strictAssert(window.textsecure.server, 'server must exist');
|
||||||
|
let downloaded: ReencryptedAttachmentV2 | undefined;
|
||||||
|
try {
|
||||||
|
downloaded = await downloadAttachment(
|
||||||
|
window.textsecure.server,
|
||||||
|
contactAttachment,
|
||||||
|
{
|
||||||
|
disableRetries: true,
|
||||||
|
timeout: 90 * SECOND,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return await parseContactsV2({
|
||||||
|
...contactAttachment,
|
||||||
|
...downloaded,
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
if (downloaded?.path) {
|
||||||
|
await window.Signal.Migrations.deleteAttachmentData(downloaded.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function doContactSync({
|
async function doContactSync({
|
||||||
contacts,
|
contactAttachment,
|
||||||
complete: isFullSync,
|
complete: isFullSync,
|
||||||
receivedAtCounter,
|
receivedAtCounter,
|
||||||
sentAt,
|
sentAt,
|
||||||
|
@ -95,8 +129,11 @@ async function doContactSync({
|
||||||
const logId =
|
const logId =
|
||||||
`doContactSync(sent=${sentAt}, ` +
|
`doContactSync(sent=${sentAt}, ` +
|
||||||
`receivedAt=${receivedAtCounter}, isFullSync=${isFullSync})`;
|
`receivedAt=${receivedAtCounter}, isFullSync=${isFullSync})`;
|
||||||
log.info(`${logId}: got ${contacts.length} contacts`);
|
|
||||||
|
|
||||||
|
log.info(`${logId}: downloading contact attachment`);
|
||||||
|
const contacts = await downloadAndParseContactAttachment(contactAttachment);
|
||||||
|
|
||||||
|
log.info(`${logId}: got ${contacts.length} contacts`);
|
||||||
const updatedConversations = new Set<ConversationModel>();
|
const updatedConversations = new Set<ConversationModel>();
|
||||||
|
|
||||||
let promises = new Array<Promise<void>>();
|
let promises = new Array<Promise<void>>();
|
||||||
|
@ -195,5 +232,12 @@ export async function onContactSync(ev: ContactSyncEvent): Promise<void> {
|
||||||
log.info(
|
log.info(
|
||||||
`onContactSync(sent=${ev.sentAt}, receivedAt=${ev.receivedAtCounter}): queueing sync`
|
`onContactSync(sent=${ev.sentAt}, receivedAt=${ev.receivedAtCounter}): queueing sync`
|
||||||
);
|
);
|
||||||
await queue.add(() => doContactSync(ev));
|
|
||||||
|
const promise = queue.add(() => doContactSync(ev));
|
||||||
|
|
||||||
|
// Returning the promise blocks MessageReceiver.appQueue, which we only want to do on
|
||||||
|
// initial sync
|
||||||
|
if (isInitialSync) {
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@ import Long from 'long';
|
||||||
|
|
||||||
import MessageReceiver from '../textsecure/MessageReceiver';
|
import MessageReceiver from '../textsecure/MessageReceiver';
|
||||||
import { IncomingWebSocketRequestLegacy } from '../textsecure/WebsocketResources';
|
import { IncomingWebSocketRequestLegacy } from '../textsecure/WebsocketResources';
|
||||||
import type { WebAPIType } from '../textsecure/WebAPI';
|
|
||||||
import type { DecryptionErrorEvent } from '../textsecure/messageReceiverEvents';
|
import type { DecryptionErrorEvent } from '../textsecure/messageReceiverEvents';
|
||||||
import { generateAci } from '../types/ServiceId';
|
import { generateAci } from '../types/ServiceId';
|
||||||
import type { AciString } from '../types/ServiceId';
|
import type { AciString } from '../types/ServiceId';
|
||||||
|
@ -39,7 +38,6 @@ describe('MessageReceiver', () => {
|
||||||
describe('connecting', () => {
|
describe('connecting', () => {
|
||||||
it('generates decryption-error event when it cannot decrypt', async () => {
|
it('generates decryption-error event when it cannot decrypt', async () => {
|
||||||
const messageReceiver = new MessageReceiver({
|
const messageReceiver = new MessageReceiver({
|
||||||
server: {} as WebAPIType,
|
|
||||||
storage: window.storage,
|
storage: window.storage,
|
||||||
serverTrustRoot: 'AAAAAAAA',
|
serverTrustRoot: 'AAAAAAAA',
|
||||||
});
|
});
|
||||||
|
|
|
@ -49,8 +49,7 @@ import { parseIntOrThrow } from '../util/parseIntOrThrow';
|
||||||
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
|
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
|
||||||
import { Zone } from '../util/Zone';
|
import { Zone } from '../util/Zone';
|
||||||
import * as durations from '../util/durations';
|
import * as durations from '../util/durations';
|
||||||
import { DurationInSeconds, SECOND } from '../util/durations';
|
import { DurationInSeconds } from '../util/durations';
|
||||||
import type { AttachmentType } from '../types/Attachment';
|
|
||||||
import { Address } from '../types/Address';
|
import { Address } from '../types/Address';
|
||||||
import { QualifiedAddress } from '../types/QualifiedAddress';
|
import { QualifiedAddress } from '../types/QualifiedAddress';
|
||||||
import { normalizeStoryDistributionId } from '../types/StoryDistributionId';
|
import { normalizeStoryDistributionId } from '../types/StoryDistributionId';
|
||||||
|
@ -82,11 +81,8 @@ import {
|
||||||
import { processSyncMessage } from './processSyncMessage';
|
import { processSyncMessage } from './processSyncMessage';
|
||||||
import type { EventHandler } from './EventTarget';
|
import type { EventHandler } from './EventTarget';
|
||||||
import EventTarget from './EventTarget';
|
import EventTarget from './EventTarget';
|
||||||
import { downloadAttachment } from './downloadAttachment';
|
|
||||||
import type { IncomingWebSocketRequest } from './WebsocketResources';
|
import type { IncomingWebSocketRequest } from './WebsocketResources';
|
||||||
import { ServerRequestType } from './WebsocketResources';
|
import { ServerRequestType } from './WebsocketResources';
|
||||||
import { parseContactsV2 } from './ContactsParser';
|
|
||||||
import type { WebAPIType } from './WebAPI';
|
|
||||||
import type { Storage } from './Storage';
|
import type { Storage } from './Storage';
|
||||||
import { WarnOnlyError } from './Errors';
|
import { WarnOnlyError } from './Errors';
|
||||||
import * as Bytes from '../Bytes';
|
import * as Bytes from '../Bytes';
|
||||||
|
@ -215,7 +211,6 @@ enum TaskType {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MessageReceiverOptions = {
|
export type MessageReceiverOptions = {
|
||||||
server: WebAPIType;
|
|
||||||
storage: Storage;
|
storage: Storage;
|
||||||
serverTrustRoot: string;
|
serverTrustRoot: string;
|
||||||
};
|
};
|
||||||
|
@ -287,8 +282,6 @@ export default class MessageReceiver
|
||||||
{
|
{
|
||||||
/* eslint-enable @typescript-eslint/brace-style */
|
/* eslint-enable @typescript-eslint/brace-style */
|
||||||
|
|
||||||
private server: WebAPIType;
|
|
||||||
|
|
||||||
private storage: Storage;
|
private storage: Storage;
|
||||||
|
|
||||||
private appQueue: PQueue;
|
private appQueue: PQueue;
|
||||||
|
@ -319,10 +312,9 @@ export default class MessageReceiver
|
||||||
|
|
||||||
private isAppReadyForProcessing: boolean = false;
|
private isAppReadyForProcessing: boolean = false;
|
||||||
|
|
||||||
constructor({ server, storage, serverTrustRoot }: MessageReceiverOptions) {
|
constructor({ storage, serverTrustRoot }: MessageReceiverOptions) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.server = server;
|
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
|
|
||||||
this.count = 0;
|
this.count = 0;
|
||||||
|
@ -3823,34 +3815,13 @@ export default class MessageReceiver
|
||||||
|
|
||||||
this.removeFromCache(envelope);
|
this.removeFromCache(envelope);
|
||||||
|
|
||||||
let attachment: AttachmentType | undefined;
|
const contactSync = new ContactSyncEvent(
|
||||||
try {
|
processAttachment(blob),
|
||||||
attachment = await this.handleAttachmentV2(blob, {
|
Boolean(contactSyncProto.complete),
|
||||||
disableRetries: true,
|
envelope.receivedAtCounter,
|
||||||
timeout: 90 * SECOND,
|
envelope.timestamp
|
||||||
});
|
);
|
||||||
|
await this.dispatchAndWait(logId, contactSync);
|
||||||
const { path } = attachment;
|
|
||||||
if (!path) {
|
|
||||||
throw new Error('Failed no path field in returned attachment');
|
|
||||||
}
|
|
||||||
|
|
||||||
const contacts = await parseContactsV2(attachment);
|
|
||||||
|
|
||||||
const contactSync = new ContactSyncEvent(
|
|
||||||
contacts,
|
|
||||||
Boolean(contactSyncProto.complete),
|
|
||||||
envelope.receivedAtCounter,
|
|
||||||
envelope.timestamp
|
|
||||||
);
|
|
||||||
await this.dispatchAndWait(logId, contactSync);
|
|
||||||
|
|
||||||
log.info('handleContacts: finished');
|
|
||||||
} finally {
|
|
||||||
if (attachment?.path) {
|
|
||||||
await window.Signal.Migrations.deleteAttachmentData(attachment.path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleBlocked(
|
private async handleBlocked(
|
||||||
|
@ -3937,18 +3908,6 @@ export default class MessageReceiver
|
||||||
return this.storage.blocked.isGroupBlocked(groupId);
|
return this.storage.blocked.isGroupBlocked(groupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleAttachmentV2(
|
|
||||||
attachment: Proto.IAttachmentPointer,
|
|
||||||
options?: { timeout?: number; disableRetries?: boolean }
|
|
||||||
): Promise<AttachmentType> {
|
|
||||||
const cleaned = processAttachment(attachment);
|
|
||||||
const downloaded = await downloadAttachment(this.server, cleaned, options);
|
|
||||||
return {
|
|
||||||
...cleaned,
|
|
||||||
...downloaded,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private async handleEndSession(
|
private async handleEndSession(
|
||||||
envelope: ProcessedEnvelope,
|
envelope: ProcessedEnvelope,
|
||||||
theirServiceId: ServiceIdString
|
theirServiceId: ServiceIdString
|
||||||
|
|
|
@ -16,8 +16,8 @@ import type {
|
||||||
ProcessedEnvelope,
|
ProcessedEnvelope,
|
||||||
ProcessedDataMessage,
|
ProcessedDataMessage,
|
||||||
ProcessedSent,
|
ProcessedSent,
|
||||||
|
ProcessedAttachment,
|
||||||
} from './Types.d';
|
} from './Types.d';
|
||||||
import type { ContactDetailsWithAvatar } from './ContactsParser';
|
|
||||||
import type {
|
import type {
|
||||||
CallEventDetails,
|
CallEventDetails,
|
||||||
CallLogEventDetails,
|
CallLogEventDetails,
|
||||||
|
@ -84,7 +84,7 @@ export class ErrorEvent extends Event {
|
||||||
|
|
||||||
export class ContactSyncEvent extends Event {
|
export class ContactSyncEvent extends Event {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly contacts: ReadonlyArray<ContactDetailsWithAvatar>,
|
public readonly contactAttachment: ProcessedAttachment,
|
||||||
public readonly complete: boolean,
|
public readonly complete: boolean,
|
||||||
public readonly receivedAtCounter: number,
|
public readonly receivedAtCounter: number,
|
||||||
public readonly sentAt: number
|
public readonly sentAt: number
|
||||||
|
|
Loading…
Reference in a new issue