Disallow conversation model creation during import

This commit is contained in:
Fedor Indutny 2025-01-27 12:16:50 -08:00 committed by GitHub
parent 65055fd475
commit 3b78c9885a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 60 additions and 8 deletions

View file

@ -150,6 +150,7 @@ export function start(): void {
export class ConversationController { export class ConversationController {
#_initialFetchComplete = false; #_initialFetchComplete = false;
#isReadOnly = false;
private _initialPromise: undefined | Promise<void>; private _initialPromise: undefined | Promise<void>;
@ -291,6 +292,10 @@ export class ConversationController {
return conversation; return conversation;
} }
if (this.#isReadOnly) {
throw new Error('ConversationController is read-only');
}
const id = generateUuid(); const id = generateUuid();
if (type === 'group') { if (type === 'group') {
@ -1299,6 +1304,16 @@ export class ConversationController {
); );
} }
setReadOnly(value: boolean): void {
if (this.#isReadOnly === value) {
log.warn(`ConversationController: already at readOnly=${value}`);
return;
}
log.info(`ConversationController: readOnly=${value}`);
this.#isReadOnly = value;
}
reset(): void { reset(): void {
delete this._initialPromise; delete this._initialPromise;
this.#_initialFetchComplete = false; this.#_initialFetchComplete = false;

View file

@ -1513,10 +1513,10 @@ export async function startApp(): Promise<void> {
}); });
void updateExpiringMessagesService(); void updateExpiringMessagesService();
void tapToViewMessagesDeletionService.update(); tapToViewMessagesDeletionService.update();
window.Whisper.events.on('timetravel', () => { window.Whisper.events.on('timetravel', () => {
void updateExpiringMessagesService(); void updateExpiringMessagesService();
void tapToViewMessagesDeletionService.update(); tapToViewMessagesDeletionService.update();
}); });
const isCoreDataValid = Boolean( const isCoreDataValid = Boolean(
@ -1652,6 +1652,8 @@ export async function startApp(): Promise<void> {
const backupDownloadPath = window.storage.get('backupDownloadPath'); const backupDownloadPath = window.storage.get('backupDownloadPath');
if (backupDownloadPath) { if (backupDownloadPath) {
tapToViewMessagesDeletionService.pause();
// Download backup before enabling request handler and storage service // Download backup before enabling request handler and storage service
try { try {
await backupsService.downloadAndImport({ await backupsService.downloadAndImport({
@ -1670,6 +1672,8 @@ export async function startApp(): Promise<void> {
log.error('afterStart: backup download failed, rejecting'); log.error('afterStart: backup download failed, rejecting');
backupReady.reject(error); backupReady.reject(error);
throw error; throw error;
} finally {
tapToViewMessagesDeletionService.resume();
} }
} else { } else {
backupReady.resolve(); backupReady.resolve();

View file

@ -343,6 +343,7 @@ export class BackupImportStream extends Writable {
} }
// Reset and reload conversations and storage again // Reset and reload conversations and storage again
window.ConversationController.setReadOnly(false);
window.ConversationController.reset(); window.ConversationController.reset();
await window.ConversationController.load(); await window.ConversationController.load();

View file

@ -350,6 +350,8 @@ export class BackupsService {
await DataWriter.disableMessageInsertTriggers(); await DataWriter.disableMessageInsertTriggers();
try { try {
window.ConversationController.setReadOnly(true);
const importStream = await BackupImportStream.create(backupType); const importStream = await BackupImportStream.create(backupType);
if (backupType === BackupType.Ciphertext) { if (backupType === BackupType.Ciphertext) {
const { aesKey, macKey } = getKeyMaterial( const { aesKey, macKey } = getKeyMaterial(
@ -440,6 +442,7 @@ export class BackupsService {
throw error; throw error;
} finally { } finally {
window.ConversationController.setReadOnly(false);
this.#isRunning = false; this.#isRunning = false;
await DataWriter.enableMessageInsertTriggersAndBackfill(); await DataWriter.enableMessageInsertTriggersAndBackfill();

View file

@ -10,6 +10,7 @@ import { strictAssert } from '../util/assert';
import { toBoundedDate } from '../util/timestamp'; import { toBoundedDate } from '../util/timestamp';
import { getMessageIdForLogging } from '../util/idForLogging'; import { getMessageIdForLogging } from '../util/idForLogging';
import { eraseMessageContents } from '../util/cleanup'; import { eraseMessageContents } from '../util/cleanup';
import { drop } from '../util/drop';
import { MessageModel } from '../models/messages'; import { MessageModel } from '../models/messages';
async function eraseTapToViewMessages() { async function eraseTapToViewMessages() {
@ -53,12 +54,38 @@ async function eraseTapToViewMessages() {
} }
class TapToViewMessagesDeletionService { class TapToViewMessagesDeletionService {
public update: () => Promise<void>;
#timeout?: ReturnType<typeof setTimeout>; #timeout?: ReturnType<typeof setTimeout>;
#isPaused = false;
#debouncedUpdate = debounce(this.#checkTapToViewMessages);
constructor() { update() {
this.update = debounce(this.#checkTapToViewMessages, 1000); drop(this.#debouncedUpdate());
}
pause(): void {
if (this.#isPaused) {
window.SignalContext.log.warn('checkTapToViewMessages: already paused');
return;
}
window.SignalContext.log.info('checkTapToViewMessages: pause');
this.#isPaused = true;
clearTimeoutIfNecessary(this.#timeout);
this.#timeout = undefined;
}
resume(): void {
if (!this.#isPaused) {
window.SignalContext.log.warn('checkTapToViewMessages: not paused');
return;
}
window.SignalContext.log.info('checkTapToViewMessages: resuming');
this.#isPaused = false;
this.#debouncedUpdate.cancel();
this.update();
} }
async #checkTapToViewMessages() { async #checkTapToViewMessages() {
@ -89,8 +116,10 @@ class TapToViewMessagesDeletionService {
clearTimeoutIfNecessary(this.#timeout); clearTimeoutIfNecessary(this.#timeout);
this.#timeout = setTimeout(async () => { this.#timeout = setTimeout(async () => {
await eraseTapToViewMessages(); if (!this.#isPaused && !window.SignalContext.isTestOrMockEnvironment()) {
void this.update(); await eraseTapToViewMessages();
}
this.update();
}, wait); }, wait);
} }
} }