diff --git a/lib/backup/user-data-restore-context.js b/lib/backup/user-data-restore-context.js --- a/lib/backup/user-data-restore-context.js +++ b/lib/backup/user-data-restore-context.js @@ -12,8 +12,7 @@ import { setClientDBStoreActionType } from '../actions/client-db-store-actions.js'; import { generateOpsToEstablishHoldersOnDevice } from '../actions/holder-actions.js'; import { logTypes, useDebugLogs } from '../components/debug-logs-context.js'; -import type { RestoreUserDataStep } from '../types/backup-types.js'; -import { restoreUserDataSteps } from '../types/backup-types.js'; +import { restoreUserDataStepsOrder } from '../types/backup-types.js'; import { databaseIdentifier } from '../types/database-identifier-types.js'; import type { QRAuthBackupData } from '../types/tunnelbroker/qr-code-auth-message-types.js'; import { getConfig } from '../utils/config.js'; @@ -23,6 +22,7 @@ type UserDataRestoreContextType = { +userDataRestore: ( + isPrimary: boolean, userID: string, accessToken: string, backupData: ?QRAuthBackupData, @@ -47,6 +47,7 @@ const executeRestoreUserData = React.useCallback( async ( + isPrimary: boolean, userID: string, accessToken: string, backupData: ?QRAuthBackupData, @@ -54,17 +55,10 @@ const { sqliteAPI } = getConfig(); // Determine starting step based on current state - const stepOrder: $ReadOnlyArray = [ - restoreUserDataSteps.RESTORE_DATABASE, - restoreUserDataSteps.MIGRATE_BACKUP_SCHEMA, - restoreUserDataSteps.RUN_RESTORED_BACKUP_MIGRATIONS, - restoreUserDataSteps.ASSIGN_NEW_HOLDERS, - restoreUserDataSteps.COPY_CONTENT_FROM_BACKUP_DB, - ]; let startStepIndex = 0; if (restoreBackupState.status === 'user_data_restore_step_completed') { - const completedStepIndex = stepOrder.indexOf( + const completedStepIndex = restoreUserDataStepsOrder.indexOf( restoreBackupState.payload.step, ); startStepIndex = completedStepIndex + 1; @@ -72,14 +66,16 @@ restoreBackupState.status === 'user_data_restore_started' || restoreBackupState.status === 'user_data_restore_failed' ) { - startStepIndex = stepOrder.indexOf(restoreBackupState.payload.step); + startStepIndex = restoreUserDataStepsOrder.indexOf( + restoreBackupState.payload.step, + ); } else { // for any other state, start from scratch startStepIndex = 0; } // if all steps from last restore succeeded, start from scratch - if (startStepIndex > 4) { + if (startStepIndex > 5) { startStepIndex = 0; } @@ -232,8 +228,24 @@ await assignHoldersPromise; } - // 5. Copy content to main database + // 5. Remove local message infos if (startStepIndex <= 4) { + const removeLocalMessageInfosPromise = + sqliteAPI.removeLocalMessageInfos( + isPrimary, + databaseIdentifier.RESTORED, + ); + void dispatchActionPromise( + restoreUserDataStepActionTypes, + removeLocalMessageInfosPromise, + undefined, + { step: 'remove_local_message_infos' }, + ); + await removeLocalMessageInfosPromise; + } + + // 6. Copy content to main database + if (startStepIndex <= 5) { const copyContentPromise = sqliteAPI.copyContentFromBackupDatabase(); void dispatchActionPromise( restoreUserDataStepActionTypes, @@ -244,7 +256,7 @@ await copyContentPromise; } - // 6. Populate store + // 7. Populate store const clientDBStore = await sqliteAPI.getClientDBStore( databaseIdentifier.MAIN, userID, @@ -260,6 +272,7 @@ const userDataRestore = React.useCallback( async ( + isPrimary: boolean, userID: string, accessToken: string, backupData: ?QRAuthBackupData, @@ -270,7 +283,12 @@ const promise = (async () => { try { - await executeRestoreUserData(userID, accessToken, backupData); + await executeRestoreUserData( + isPrimary, + userID, + accessToken, + backupData, + ); } finally { restorePromiseRef.current = null; } diff --git a/lib/components/secondary-device-qr-auth-context-provider.react.js b/lib/components/secondary-device-qr-auth-context-provider.react.js --- a/lib/components/secondary-device-qr-auth-context-provider.react.js +++ b/lib/components/secondary-device-qr-auth-context-provider.react.js @@ -205,6 +205,7 @@ throw new Error('Missing backupData'); } await userDataRestore( + false, identityAuthResult.userID, identityAuthResult.accessToken, backupData, diff --git a/lib/handlers/restore-backup-handler.react.js b/lib/handlers/restore-backup-handler.react.js --- a/lib/handlers/restore-backup-handler.react.js +++ b/lib/handlers/restore-backup-handler.react.js @@ -3,6 +3,7 @@ import * as React from 'react'; import { useUserDataRestoreContext } from '../backup/user-data-restore-context.js'; +import { useDeviceKind } from '../hooks/primary-device-hooks.js'; import { usePersistedStateLoaded } from '../selectors/app-state-selectors.js'; import { useSelector } from '../utils/redux-utils.js'; import { fullBackupSupport } from '../utils/services-utils.js'; @@ -15,6 +16,7 @@ const userID = useSelector(state => state.currentUserInfo?.id); const accessToken = useSelector(state => state.commServicesAccessToken); const { userDataRestore } = useUserDataRestoreContext(); + const deviceKind = useDeviceKind(); React.useEffect(() => { if ( @@ -22,7 +24,8 @@ !persistedStateLoaded || executed.current || !userID || - !accessToken + !accessToken || + deviceKind === 'unknown' ) { return; } @@ -33,10 +36,11 @@ return; } - void userDataRestore(userID, accessToken); + void userDataRestore(deviceKind === 'primary', userID, accessToken); executed.current = true; }, [ accessToken, + deviceKind, persistedStateLoaded, restoreBackupState.status, userDataRestore, diff --git a/lib/reducers/backup-reducer.js b/lib/reducers/backup-reducer.js --- a/lib/reducers/backup-reducer.js +++ b/lib/reducers/backup-reducer.js @@ -16,10 +16,10 @@ restoreUserActionTypes, } from '../actions/user-actions.js'; import { - restoreUserDataSteps, type BackupStore, type RestoreBackupState, type RestoreUserDataStep, + restoreUserDataStepsOrder, } from '../types/backup-types.js'; import type { BaseAction } from '../types/redux-types.js'; import { fullBackupSupport } from '../utils/services-utils.js'; @@ -171,21 +171,16 @@ return store; } -const stepOrder: $ReadOnlyArray = [ - restoreUserDataSteps.RESTORE_DATABASE, - restoreUserDataSteps.MIGRATE_BACKUP_SCHEMA, - restoreUserDataSteps.RUN_RESTORED_BACKUP_MIGRATIONS, - restoreUserDataSteps.ASSIGN_NEW_HOLDERS, - restoreUserDataSteps.COPY_CONTENT_FROM_BACKUP_DB, -]; function validateUserDataRestoreStepOrder( store: RestoreBackupState, step: RestoreUserDataStep, ) { - const currentStepIndex = stepOrder.indexOf(step); + const currentStepIndex = restoreUserDataStepsOrder.indexOf(step); if (store.status === 'user_data_restore_step_completed') { - const lastCompletedStepIndex = stepOrder.indexOf(store.payload.step); + const lastCompletedStepIndex = restoreUserDataStepsOrder.indexOf( + store.payload.step, + ); invariant( currentStepIndex === lastCompletedStepIndex + 1, `Invalid step order: trying to start '${step}' but last completed step was '${store.payload.step}'`, @@ -195,7 +190,8 @@ store.status === 'user_data_restore_failed' ) { invariant( - currentStepIndex === stepOrder.indexOf(store.payload.step), + currentStepIndex === + restoreUserDataStepsOrder.indexOf(store.payload.step), `Invalid step: trying to restart '${step}' but current step is '${store.payload.step}'`, ); } else { diff --git a/lib/tunnelbroker/use-peer-to-peer-message-handler.js b/lib/tunnelbroker/use-peer-to-peer-message-handler.js --- a/lib/tunnelbroker/use-peer-to-peer-message-handler.js +++ b/lib/tunnelbroker/use-peer-to-peer-message-handler.js @@ -205,7 +205,7 @@ return; } - await userDataRestore(userID, accessToken, backupData); + await userDataRestore(false, userID, accessToken, backupData); await sqliteAPI.removeInboundP2PMessages([messageID]); } else { diff --git a/lib/types/backup-types.js b/lib/types/backup-types.js --- a/lib/types/backup-types.js +++ b/lib/types/backup-types.js @@ -88,10 +88,20 @@ MIGRATE_BACKUP_SCHEMA: 'migrate_backup_schema', RUN_RESTORED_BACKUP_MIGRATIONS: 'run_restored_backup_migrations', ASSIGN_NEW_HOLDERS: 'assign_new_holders', + REMOVE_LOCAL_MESSAGE_INFOS: 'remove_local_message_infos', COPY_CONTENT_FROM_BACKUP_DB: 'copy_content_from_backup_db', }); export type RestoreUserDataStep = $Values; +export const restoreUserDataStepsOrder: $ReadOnlyArray = [ + restoreUserDataSteps.RESTORE_DATABASE, + restoreUserDataSteps.MIGRATE_BACKUP_SCHEMA, + restoreUserDataSteps.RUN_RESTORED_BACKUP_MIGRATIONS, + restoreUserDataSteps.ASSIGN_NEW_HOLDERS, + restoreUserDataSteps.REMOVE_LOCAL_MESSAGE_INFOS, + restoreUserDataSteps.COPY_CONTENT_FROM_BACKUP_DB, +]; + export type RestoreBackupState = | { +status: 'no_backup', diff --git a/lib/types/sqlite-types.js b/lib/types/sqlite-types.js --- a/lib/types/sqlite-types.js +++ b/lib/types/sqlite-types.js @@ -94,6 +94,10 @@ messageID: string, deviceID: string, ) => Promise, + +removeLocalMessageInfos: ( + includeNonLocalMessages: boolean, + dbID: DatabaseIdentifier, + ) => Promise, +processDBStoreOperations: ( operations: StoreOperations, diff --git a/lib/utils/__mocks__/config.js b/lib/utils/__mocks__/config.js --- a/lib/utils/__mocks__/config.js +++ b/lib/utils/__mocks__/config.js @@ -54,6 +54,7 @@ getDatabaseVersion: jest.fn(), getSyncedMetadata: jest.fn(), getHolders: jest.fn(), + removeLocalMessageInfos: jest.fn(), }, encryptedNotifUtilsAPI: { generateAESKey: jest.fn(), diff --git a/native/account/restore.js b/native/account/restore.js --- a/native/account/restore.js +++ b/native/account/restore.js @@ -220,6 +220,7 @@ } const backupData = await commCoreModule.getQRAuthBackupData(); await userDataRestore( + true, identityAuthResult.userID, identityAuthResult.accessToken, backupData, diff --git a/native/cpp/CommonCpp/DatabaseManagers/DatabaseQueryExecutor.h b/native/cpp/CommonCpp/DatabaseManagers/DatabaseQueryExecutor.h --- a/native/cpp/CommonCpp/DatabaseManagers/DatabaseQueryExecutor.h +++ b/native/cpp/CommonCpp/DatabaseManagers/DatabaseQueryExecutor.h @@ -212,6 +212,7 @@ virtual void replaceHolder(const Holder &holder) const = 0; virtual void removeHolders(const std::vector &hashes) const = 0; virtual std::vector getHolders() const = 0; + virtual void removeLocalMessageInfos(bool includeNonLocalMessages) const = 0; virtual ~DatabaseQueryExecutor() = default; }; diff --git a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h --- a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h +++ b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h @@ -203,6 +203,7 @@ void replaceHolder(const Holder &holder) const override; void removeHolders(const std::vector &hashes) const override; std::vector getHolders() const override; + void removeLocalMessageInfos(bool includeNonLocalMessages) const override; }; } // namespace comm diff --git a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp --- a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp +++ b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp @@ -1813,4 +1813,24 @@ return getAllEntities(this->getConnection(), query); } +void SQLiteQueryExecutor::removeLocalMessageInfos( + bool includeNonLocalMessages) const { + if (!includeNonLocalMessages) { + static std::string removeLocalQuery = + "UPDATE backup_messages " + "SET local_id = NULL " + "WHERE id IN " + " (SELECT id FROM backup_message_store_local); " + "DELETE FROM backup_message_store_local;"; + executeQuery(this->getConnection(), removeLocalQuery); + } else { + static std::string removeWithNonLocalQuery = + "DELETE FROM backup_messages " + "WHERE id IN " + " (SELECT id FROM backup_message_store_local); " + "DELETE FROM backup_message_store_local;"; + executeQuery(this->getConnection(), removeWithNonLocalQuery); + } +} + } // namespace comm diff --git a/native/cpp/CommonCpp/NativeModules/CommCoreModule.h b/native/cpp/CommonCpp/NativeModules/CommCoreModule.h --- a/native/cpp/CommonCpp/NativeModules/CommCoreModule.h +++ b/native/cpp/CommonCpp/NativeModules/CommCoreModule.h @@ -264,6 +264,10 @@ jsi::String entryName, jsi::String dbID) override; virtual jsi::Value getHolders(jsi::Runtime &rt, jsi::String dbID) override; + virtual jsi::Value removeLocalMessageInfos( + jsi::Runtime &rt, + bool includeNonLocalMessages, + jsi::String dbID) override; virtual jsi::Value markPrekeysAsPublished(jsi::Runtime &rt) override; virtual jsi::Value getRelatedMessages(jsi::Runtime &rt, jsi::String messageID) override; diff --git a/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp b/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp --- a/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp +++ b/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp @@ -3154,6 +3154,38 @@ }); } +jsi::Value CommCoreModule::removeLocalMessageInfos( + jsi::Runtime &rt, + bool includeNonLocalMessages, + jsi::String dbID) { + DatabaseIdentifier identifier = stringToDatabaseIdentifier(dbID.utf8(rt)); + return createPromiseAsJSIValue( + rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { + taskType job = [=, &innerRt]() { + std::string error; + try { + DatabaseManager::getQueryExecutor(identifier).beginTransaction(); + DatabaseManager::getQueryExecutor(identifier) + .removeLocalMessageInfos(includeNonLocalMessages); + DatabaseManager::getQueryExecutor(identifier).commitTransaction(); + } catch (std::system_error &e) { + error = e.what(); + DatabaseManager::getQueryExecutor(identifier).rollbackTransaction(); + } + + this->jsInvoker_->invokeAsync([&innerRt, error, promise]() { + if (error.size()) { + promise->reject(error); + return; + } + promise->resolve(jsi::Value::undefined()); + }); + }; + GlobalDBSingleton::instance.scheduleOrRunCancellable( + job, promise, this->jsInvoker_); + }); +} + jsi::Value CommCoreModule::markPrekeysAsPublished(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { diff --git a/native/cpp/CommonCpp/_generated/commJSI-generated.cpp b/native/cpp/CommonCpp/_generated/commJSI-generated.cpp --- a/native/cpp/CommonCpp/_generated/commJSI-generated.cpp +++ b/native/cpp/CommonCpp/_generated/commJSI-generated.cpp @@ -241,6 +241,9 @@ static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getHolders(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->getHolders(rt, args[0].asString(rt)); } +static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_removeLocalMessageInfos(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + return static_cast(&turboModule)->removeLocalMessageInfos(rt, args[0].asBool(), args[1].asString(rt)); +} CommCoreModuleSchemaCxxSpecJSI::CommCoreModuleSchemaCxxSpecJSI(std::shared_ptr jsInvoker) : TurboModule("CommTurboModule", jsInvoker) { @@ -318,6 +321,7 @@ methodMap_["migrateBackupSchema"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_migrateBackupSchema}; methodMap_["copyContentFromBackupDatabase"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_copyContentFromBackupDatabase}; methodMap_["getHolders"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getHolders}; + methodMap_["removeLocalMessageInfos"] = MethodMetadata {2, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_removeLocalMessageInfos}; } diff --git a/native/cpp/CommonCpp/_generated/commJSI.h b/native/cpp/CommonCpp/_generated/commJSI.h --- a/native/cpp/CommonCpp/_generated/commJSI.h +++ b/native/cpp/CommonCpp/_generated/commJSI.h @@ -94,6 +94,7 @@ virtual jsi::Value migrateBackupSchema(jsi::Runtime &rt) = 0; virtual jsi::Value copyContentFromBackupDatabase(jsi::Runtime &rt) = 0; virtual jsi::Value getHolders(jsi::Runtime &rt, jsi::String dbID) = 0; + virtual jsi::Value removeLocalMessageInfos(jsi::Runtime &rt, bool includeNonLocalMessages, jsi::String dbID) = 0; }; @@ -707,6 +708,14 @@ return bridging::callFromJs( rt, &T::getHolders, jsInvoker_, instance_, std::move(dbID)); } + jsi::Value removeLocalMessageInfos(jsi::Runtime &rt, bool includeNonLocalMessages, jsi::String dbID) override { + static_assert( + bridging::getParameterCount(&T::removeLocalMessageInfos) == 3, + "Expected removeLocalMessageInfos(...) to have 3 parameters"); + + return bridging::callFromJs( + rt, &T::removeLocalMessageInfos, jsInvoker_, instance_, std::move(includeNonLocalMessages), std::move(dbID)); + } private: T *instance_; diff --git a/native/database/sqlite-api.js b/native/database/sqlite-api.js --- a/native/database/sqlite-api.js +++ b/native/database/sqlite-api.js @@ -89,6 +89,7 @@ }, migrateBackupSchema: commCoreModule.migrateBackupSchema, copyContentFromBackupDatabase: commCoreModule.copyContentFromBackupDatabase, + removeLocalMessageInfos: commCoreModule.removeLocalMessageInfos, }; export { sqliteAPI }; diff --git a/native/schema/CommCoreModuleSchema.js b/native/schema/CommCoreModuleSchema.js --- a/native/schema/CommCoreModuleSchema.js +++ b/native/schema/CommCoreModuleSchema.js @@ -241,6 +241,10 @@ +migrateBackupSchema: () => Promise; +copyContentFromBackupDatabase: () => Promise; +getHolders: (dbID: string) => Promise<$ReadOnlyArray>; + +removeLocalMessageInfos: ( + includeNonLocalMessages: boolean, + dbID: string, + ) => Promise; } export interface CoreModuleSpec extends Spec { diff --git a/web/cpp/SQLiteQueryExecutorBindings.cpp b/web/cpp/SQLiteQueryExecutorBindings.cpp --- a/web/cpp/SQLiteQueryExecutorBindings.cpp +++ b/web/cpp/SQLiteQueryExecutorBindings.cpp @@ -352,7 +352,10 @@ .function("getSyncedMetadata", &SQLiteQueryExecutor::getSyncedMetadata) .function("replaceHolder", &SQLiteQueryExecutor::replaceHolder) .function("removeHolders", &SQLiteQueryExecutor::removeHolders) - .function("getHolders", &SQLiteQueryExecutor::getHolders); + .function("getHolders", &SQLiteQueryExecutor::getHolders) + .function( + "removeLocalMessageInfos", + &SQLiteQueryExecutor::removeLocalMessageInfos); class_("SQLiteBackup") .class_function( diff --git a/web/database/sqlite-api.js b/web/database/sqlite-api.js --- a/web/database/sqlite-api.js +++ b/web/database/sqlite-api.js @@ -293,6 +293,18 @@ type: workerRequestMessageTypes.COPY_CONTENT_FROM_BACKUP_DB, }); }, + + async removeLocalMessageInfos( + includeNonLocalMessages: boolean, + dbID: DatabaseIdentifier, + ): Promise { + const sharedWorker = await getCommSharedWorker(); + await sharedWorker.schedule({ + type: workerRequestMessageTypes.REMOVE_LOCAL_MESSAGE_INFOS, + includeNonLocalMessages, + dbID, + }); + }, }; export { sqliteAPI }; diff --git a/web/shared-worker/_generated/comm_query_executor.wasm b/web/shared-worker/_generated/comm_query_executor.wasm index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@): void; getHolders(): $ReadOnlyArray; + removeLocalMessageInfos(includeNonLocalMessages: boolean): void; + // method is provided to manually signal that a C++ object // is no longer needed and can be deleted delete(): void; diff --git a/web/shared-worker/worker/shared-worker.js b/web/shared-worker/worker/shared-worker.js --- a/web/shared-worker/worker/shared-worker.js +++ b/web/shared-worker/worker/shared-worker.js @@ -620,6 +620,41 @@ setSQLiteQueryExecutor(null, databaseIdentifier.RESTORED); } await localforage.removeItem(RESTORED_SQLITE_CONTENT); + } else if ( + message.type === workerRequestMessageTypes.REMOVE_LOCAL_MESSAGE_INFOS + ) { + if (message.dbID && message.dbID === databaseIdentifier.RESTORED) { + const restoredQueryExecutor = getSQLiteQueryExecutorOrThrow( + databaseIdentifier.RESTORED, + message, + ); + try { + restoredQueryExecutor.beginTransaction(); + restoredQueryExecutor.removeLocalMessageInfos( + message.includeNonLocalMessages, + ); + restoredQueryExecutor.commitTransaction(); + } catch (e) { + restoredQueryExecutor.rollbackTransaction(); + console.log( + 'Error while removing local message infos from restored DB: ', + e, + ); + throw e; + } + } else { + try { + sqliteQueryExecutor.beginTransaction(); + sqliteQueryExecutor.removeLocalMessageInfos( + message.includeNonLocalMessages, + ); + sqliteQueryExecutor.commitTransaction(); + } catch (e) { + sqliteQueryExecutor.rollbackTransaction(); + console.log('Error while removing local message infos: ', e); + throw e; + } + } } persistNeeded = true; diff --git a/web/types/worker-types.js b/web/types/worker-types.js --- a/web/types/worker-types.js +++ b/web/types/worker-types.js @@ -59,6 +59,7 @@ GET_DATABASE_VERSION: 30, GET_SYNCED_METADATA: 31, GET_HOLDERS: 32, + REMOVE_LOCAL_MESSAGE_INFOS: 33, }); export const workerWriteRequests: $ReadOnlyArray = [ @@ -74,6 +75,7 @@ workerRequestMessageTypes.RESET_OUTBOUND_P2P_MESSAGES, workerRequestMessageTypes.MIGRATE_BACKUP_SCHEMA, workerRequestMessageTypes.COPY_CONTENT_FROM_BACKUP_DB, + workerRequestMessageTypes.REMOVE_LOCAL_MESSAGE_INFOS, ]; export const workerOlmAPIRequests: $ReadOnlyArray = [ @@ -273,6 +275,12 @@ +dbID: DatabaseIdentifier, }; +export type RemoveLocalMessageInfosRequestMessage = { + +type: 33, + +includeNonLocalMessages: boolean, + +dbID: DatabaseIdentifier, +}; + export type WorkerRequestMessage = | PingWorkerRequestMessage | InitWorkerRequestMessage @@ -306,7 +314,8 @@ | CopyContentFromBackupDatabaseRequestMessage | GetDatabaseVersionRequestMessage | GetSyncedMetadataRequestMessage - | GetHoldersRequestMessage; + | GetHoldersRequestMessage + | RemoveLocalMessageInfosRequestMessage; export type WorkerRequestProxyMessage = { +id: number,