Disallow conversation model creation during import
Co-authored-by: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com>
This commit is contained in:
parent
55464045a4
commit
9f5602af05
5 changed files with 60 additions and 8 deletions
|
@ -150,6 +150,7 @@ export function start(): void {
|
|||
|
||||
export class ConversationController {
|
||||
#_initialFetchComplete = false;
|
||||
#isReadOnly = false;
|
||||
|
||||
private _initialPromise: undefined | Promise<void>;
|
||||
|
||||
|
@ -291,6 +292,10 @@ export class ConversationController {
|
|||
return conversation;
|
||||
}
|
||||
|
||||
if (this.#isReadOnly) {
|
||||
throw new Error('ConversationController is read-only');
|
||||
}
|
||||
|
||||
const id = generateUuid();
|
||||
|
||||
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 {
|
||||
delete this._initialPromise;
|
||||
this.#_initialFetchComplete = false;
|
||||
|
|
|
@ -1513,10 +1513,10 @@ export async function startApp(): Promise<void> {
|
|||
});
|
||||
|
||||
void updateExpiringMessagesService();
|
||||
void tapToViewMessagesDeletionService.update();
|
||||
tapToViewMessagesDeletionService.update();
|
||||
window.Whisper.events.on('timetravel', () => {
|
||||
void updateExpiringMessagesService();
|
||||
void tapToViewMessagesDeletionService.update();
|
||||
tapToViewMessagesDeletionService.update();
|
||||
});
|
||||
|
||||
const isCoreDataValid = Boolean(
|
||||
|
@ -1652,6 +1652,8 @@ export async function startApp(): Promise<void> {
|
|||
|
||||
const backupDownloadPath = window.storage.get('backupDownloadPath');
|
||||
if (backupDownloadPath) {
|
||||
tapToViewMessagesDeletionService.pause();
|
||||
|
||||
// Download backup before enabling request handler and storage service
|
||||
try {
|
||||
await backupsService.downloadAndImport({
|
||||
|
@ -1670,6 +1672,8 @@ export async function startApp(): Promise<void> {
|
|||
log.error('afterStart: backup download failed, rejecting');
|
||||
backupReady.reject(error);
|
||||
throw error;
|
||||
} finally {
|
||||
tapToViewMessagesDeletionService.resume();
|
||||
}
|
||||
} else {
|
||||
backupReady.resolve();
|
||||
|
|
|
@ -342,6 +342,7 @@ export class BackupImportStream extends Writable {
|
|||
}
|
||||
|
||||
// Reset and reload conversations and storage again
|
||||
window.ConversationController.setReadOnly(false);
|
||||
window.ConversationController.reset();
|
||||
|
||||
await window.ConversationController.load();
|
||||
|
|
|
@ -350,6 +350,8 @@ export class BackupsService {
|
|||
await DataWriter.disableMessageInsertTriggers();
|
||||
|
||||
try {
|
||||
window.ConversationController.setReadOnly(true);
|
||||
|
||||
const importStream = await BackupImportStream.create(backupType);
|
||||
if (backupType === BackupType.Ciphertext) {
|
||||
const { aesKey, macKey } = getKeyMaterial(
|
||||
|
@ -440,6 +442,7 @@ export class BackupsService {
|
|||
|
||||
throw error;
|
||||
} finally {
|
||||
window.ConversationController.setReadOnly(false);
|
||||
this.#isRunning = false;
|
||||
await DataWriter.enableMessageInsertTriggersAndBackfill();
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import { strictAssert } from '../util/assert';
|
|||
import { toBoundedDate } from '../util/timestamp';
|
||||
import { getMessageIdForLogging } from '../util/idForLogging';
|
||||
import { eraseMessageContents } from '../util/cleanup';
|
||||
import { drop } from '../util/drop';
|
||||
import { MessageModel } from '../models/messages';
|
||||
|
||||
async function eraseTapToViewMessages() {
|
||||
|
@ -53,12 +54,38 @@ async function eraseTapToViewMessages() {
|
|||
}
|
||||
|
||||
class TapToViewMessagesDeletionService {
|
||||
public update: () => Promise<void>;
|
||||
|
||||
#timeout?: ReturnType<typeof setTimeout>;
|
||||
#isPaused = false;
|
||||
#debouncedUpdate = debounce(this.#checkTapToViewMessages);
|
||||
|
||||
constructor() {
|
||||
this.update = debounce(this.#checkTapToViewMessages, 1000);
|
||||
update() {
|
||||
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() {
|
||||
|
@ -89,8 +116,10 @@ class TapToViewMessagesDeletionService {
|
|||
|
||||
clearTimeoutIfNecessary(this.#timeout);
|
||||
this.#timeout = setTimeout(async () => {
|
||||
await eraseTapToViewMessages();
|
||||
void this.update();
|
||||
if (!this.#isPaused && !window.SignalContext.isTestOrMockEnvironment()) {
|
||||
await eraseTapToViewMessages();
|
||||
}
|
||||
this.update();
|
||||
}, wait);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue