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 @@ -95,4 +95,6 @@ qrAuthBackupData: QRAuthBackupData, identityAuthResult: IdentityAuthResult, ) => Promise, + +migrateBackupSchema: () => Promise, + +copyContentFromBackupDatabase: () => Promise, }; 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 @@ -49,6 +49,8 @@ fetchDMOperationsByType: jest.fn(), restoreUserData: jest.fn(), getClientDBStore: jest.fn(), + copyContentFromBackupDatabase: jest.fn(), + migrateBackupSchema: jest.fn(), }, encryptedNotifUtilsAPI: { generateAESKey: jest.fn(), diff --git a/native/cpp/CommonCpp/DatabaseManagers/DatabaseManager.h b/native/cpp/CommonCpp/DatabaseManagers/DatabaseManager.h --- a/native/cpp/CommonCpp/DatabaseManagers/DatabaseManager.h +++ b/native/cpp/CommonCpp/DatabaseManagers/DatabaseManager.h @@ -69,6 +69,7 @@ std::string mainCompactionPath, std::string mainCompactionEncryptionKey, std::string maxVersion); + static void copyContentFromBackupDatabase(); }; } // namespace comm diff --git a/native/cpp/CommonCpp/DatabaseManagers/DatabaseManager.cpp b/native/cpp/CommonCpp/DatabaseManagers/DatabaseManager.cpp --- a/native/cpp/CommonCpp/DatabaseManagers/DatabaseManager.cpp +++ b/native/cpp/CommonCpp/DatabaseManagers/DatabaseManager.cpp @@ -389,7 +389,11 @@ mainCompactionPath, mainCompactionEncryptionKey, maxVersion); DatabaseManager::restoredConnectionManager = std::make_shared(backupPath); - DatabaseManager::getQueryExecutor().copyContentFromDatabase(backupPath); +} + +void DatabaseManager::copyContentFromBackupDatabase() { + DatabaseManager::getQueryExecutor().copyContentFromDatabase( + DatabaseManager::restoredConnectionManager->getSQLiteFilePath()); } } // namespace comm diff --git a/native/cpp/CommonCpp/DatabaseManagers/SQLiteConnectionManager.cpp b/native/cpp/CommonCpp/DatabaseManagers/SQLiteConnectionManager.cpp --- a/native/cpp/CommonCpp/DatabaseManagers/SQLiteConnectionManager.cpp +++ b/native/cpp/CommonCpp/DatabaseManagers/SQLiteConnectionManager.cpp @@ -55,6 +55,7 @@ void SQLiteConnectionManager::restoreFromBackupLog( const std::vector &backupLog) { + this->initializeConnection(); if (!dbConnection) { std::string errorMessage{ "Programmer error: attempt to restore from backup log, but database " 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 @@ -283,6 +283,8 @@ jsi::String backupSecret) override; virtual jsi::Value getDMOperationsByType(jsi::Runtime &rt, jsi::String type) override; + virtual jsi::Value migrateBackupSchema(jsi::Runtime &rt) override; + virtual jsi::Value copyContentFromBackupDatabase(jsi::Runtime &rt) override; public: CommCoreModule(std::shared_ptr jsInvoker); 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 @@ -3352,4 +3352,51 @@ }); } +jsi::Value CommCoreModule::migrateBackupSchema(jsi::Runtime &rt) { + return createPromiseAsJSIValue( + rt, [this](jsi::Runtime &innerRt, std::shared_ptr promise) { + taskType job = [this, &innerRt, promise]() { + std::string error; + try { + DatabaseManager::getQueryExecutor(DatabaseIdentifier::RESTORED) + .migrate(); + } catch (const std::exception &e) { + error = e.what(); + } + this->jsInvoker_->invokeAsync([&innerRt, error, promise]() { + if (error.size()) { + promise->reject(error); + } else { + promise->resolve(jsi::Value::undefined()); + } + }); + }; + GlobalDBSingleton::instance.scheduleOrRunCancellable( + job, promise, this->jsInvoker_); + }); +} + +jsi::Value CommCoreModule::copyContentFromBackupDatabase(jsi::Runtime &rt) { + return createPromiseAsJSIValue( + rt, [this](jsi::Runtime &innerRt, std::shared_ptr promise) { + taskType job = [this, &innerRt, promise]() { + std::string error; + try { + DatabaseManager::copyContentFromBackupDatabase(); + } catch (const std::exception &e) { + error = e.what(); + } + this->jsInvoker_->invokeAsync([&innerRt, error, promise]() { + if (error.size()) { + promise->reject(error); + } else { + promise->resolve(jsi::Value::undefined()); + } + }); + }; + GlobalDBSingleton::instance.scheduleOrRunCancellable( + job, promise, this->jsInvoker_); + }); +} + } // namespace comm 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 @@ -229,6 +229,12 @@ static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getDMOperationsByType(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->getDMOperationsByType(rt, args[0].asString(rt)); } +static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_migrateBackupSchema(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + return static_cast(&turboModule)->migrateBackupSchema(rt); +} +static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_copyContentFromBackupDatabase(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + return static_cast(&turboModule)->copyContentFromBackupDatabase(rt); +} CommCoreModuleSchemaCxxSpecJSI::CommCoreModuleSchemaCxxSpecJSI(std::shared_ptr jsInvoker) : TurboModule("CommTurboModule", jsInvoker) { @@ -302,6 +308,8 @@ methodMap_["fetchMessages"] = MethodMetadata {3, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_fetchMessages}; methodMap_["restoreUser"] = MethodMetadata {13, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_restoreUser}; methodMap_["getDMOperationsByType"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getDMOperationsByType}; + methodMap_["migrateBackupSchema"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_migrateBackupSchema}; + methodMap_["copyContentFromBackupDatabase"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_copyContentFromBackupDatabase}; } 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 @@ -90,6 +90,8 @@ virtual jsi::Value fetchMessages(jsi::Runtime &rt, jsi::String threadID, double limit, double offset) = 0; virtual jsi::Value restoreUser(jsi::Runtime &rt, jsi::String userID, std::optional siweSocialProofMessage, std::optional siweSocialProofSignature, jsi::String keyPayload, jsi::String keyPayloadSignature, jsi::String contentPrekey, jsi::String contentPrekeySignature, jsi::String notifPrekey, jsi::String notifPrekeySignature, jsi::Array contentOneTimeKeys, jsi::Array notifOneTimeKeys, jsi::String deviceList, jsi::String backupSecret) = 0; virtual jsi::Value getDMOperationsByType(jsi::Runtime &rt, jsi::String type) = 0; + virtual jsi::Value migrateBackupSchema(jsi::Runtime &rt) = 0; + virtual jsi::Value copyContentFromBackupDatabase(jsi::Runtime &rt) = 0; }; @@ -671,6 +673,22 @@ return bridging::callFromJs( rt, &T::getDMOperationsByType, jsInvoker_, instance_, std::move(type)); } + jsi::Value migrateBackupSchema(jsi::Runtime &rt) override { + static_assert( + bridging::getParameterCount(&T::migrateBackupSchema) == 1, + "Expected migrateBackupSchema(...) to have 1 parameters"); + + return bridging::callFromJs( + rt, &T::migrateBackupSchema, jsInvoker_, instance_); + } + jsi::Value copyContentFromBackupDatabase(jsi::Runtime &rt) override { + static_assert( + bridging::getParameterCount(&T::copyContentFromBackupDatabase) == 1, + "Expected copyContentFromBackupDatabase(...) to have 1 parameters"); + + return bridging::callFromJs( + rt, &T::copyContentFromBackupDatabase, jsInvoker_, instance_); + } 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 @@ -76,6 +76,8 @@ storeVersion.toString(), ); }, + migrateBackupSchema: commCoreModule.migrateBackupSchema, + copyContentFromBackupDatabase: commCoreModule.copyContentFromBackupDatabase, }; export { sqliteAPI }; diff --git a/native/schema/CommCoreModuleSchema.js b/native/schema/CommCoreModuleSchema.js --- a/native/schema/CommCoreModuleSchema.js +++ b/native/schema/CommCoreModuleSchema.js @@ -227,6 +227,8 @@ backupSecret: string, ) => Promise; +getDMOperationsByType: (type: string) => Promise>; + +migrateBackupSchema: () => Promise; + +copyContentFromBackupDatabase: () => Promise; } export interface CoreModuleSpec extends Spec { 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 @@ -240,6 +240,20 @@ backupLogDataKey, }); }, + + async migrateBackupSchema(): Promise { + const sharedWorker = await getCommSharedWorker(); + await sharedWorker.schedule({ + type: workerRequestMessageTypes.MIGRATE_BACKUP_SCHEMA, + }); + }, + + async copyContentFromBackupDatabase(): Promise { + const sharedWorker = await getCommSharedWorker(); + await sharedWorker.schedule({ + type: workerRequestMessageTypes.COPY_CONTENT_FROM_BACKUP_DB, + }); + }, }; 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$@ { const decryptionKey = new TextEncoder().encode(backupLogDataKey); const { userID, deviceID, accessToken } = authMetadata; if (!userID || !deviceID || !accessToken) { @@ -64,34 +61,26 @@ `${storeVersion ?? -1}`, ); - setSQLiteQueryExecutor( - new dbModule.SQLiteQueryExecutor(backupPath, true), - databaseIdentifier.RESTORED, + const restoredQueryExecutor = new dbModule.SQLiteQueryExecutor( + backupPath, + true, ); + setSQLiteQueryExecutor(restoredQueryExecutor, databaseIdentifier.RESTORED); + sqliteQueryExecutor.setPersistStorageItem( completeRootKey, reduxPersistData, ); + + await client.downloadLogs(userIdentity, backupID, async log => { + const content = await decryptCommon(crypto, decryptionKey, log); + restoredQueryExecutor.restoreFromBackupLog(content); + }); + return backupPath; } catch (err) { throw new Error(getProcessingStoreOpsExceptionMessage(err, dbModule)); } - - const restoredQueryExecutor = getSQLiteQueryExecutor( - databaseIdentifier.RESTORED, - ); - if (!restoredQueryExecutor) { - throw new Error('restoredQueryExecutor is not set'); - } - - await client.downloadLogs(userIdentity, backupID, async log => { - const content = await decryptCommon(crypto, decryptionKey, log); - try { - restoredQueryExecutor.restoreFromBackupLog(content); - } catch (err) { - throw new Error(getProcessingStoreOpsExceptionMessage(err, dbModule)); - } - }); } export { restoreBackup }; 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 @@ -2,6 +2,7 @@ import localforage from 'localforage'; +import { databaseIdentifier } from 'lib/types/database-identifier-types.js'; import { getMessageForException } from 'lib/utils/errors.js'; import { restoreBackup } from './backup.js'; @@ -61,6 +62,8 @@ let persistNeeded: boolean = false; let persistInProgress: boolean = false; +let backupPath: ?string = null; + async function initDatabase( webworkerModulesFilePath: string, commQueryExecutorFilename: ?string, @@ -367,7 +370,7 @@ ) { sqliteQueryExecutor.removePersistStorageItem(message.key); } else if (message.type === workerRequestMessageTypes.BACKUP_RESTORE) { - await restoreBackup( + backupPath = await restoreBackup( sqliteQueryExecutor, dbModule, message.authMetadata, @@ -413,6 +416,23 @@ type: workerResponseMessageTypes.RESET_OUTBOUND_P2P_MESSAGES, messageIDs, }; + } else if (message.type === workerRequestMessageTypes.MIGRATE_BACKUP_SCHEMA) { + const restoredQueryExecutor = getSQLiteQueryExecutor( + databaseIdentifier.RESTORED, + ); + if (!restoredQueryExecutor) { + throw new Error( + `restoredQueryExecutor not initialized, unable to process request type: ${message.type}`, + ); + } + restoredQueryExecutor.migrate(); + } else if ( + message.type === workerRequestMessageTypes.COPY_CONTENT_FROM_BACKUP_DB + ) { + if (!backupPath) { + throw new Error('backupPath not set'); + } + sqliteQueryExecutor.copyContentFromDatabase(backupPath); } 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 @@ -52,6 +52,8 @@ FETCH_MESSAGES: 25, GET_INBOUND_P2P_MESSAGES_BY_ID: 26, GET_DM_OPERATIONS_BY_TYPE: 27, + MIGRATE_BACKUP_SCHEMA: 28, + COPY_CONTENT_FROM_BACKUP_DB: 29, }); export const workerWriteRequests: $ReadOnlyArray = [ @@ -65,6 +67,8 @@ workerRequestMessageTypes.MARK_OUTBOUND_P2P_MESSAGE_AS_SENT, workerRequestMessageTypes.REMOVE_OUTBOUND_P2P_MESSAGE, workerRequestMessageTypes.RESET_OUTBOUND_P2P_MESSAGES, + workerRequestMessageTypes.MIGRATE_BACKUP_SCHEMA, + workerRequestMessageTypes.COPY_CONTENT_FROM_BACKUP_DB, ]; export const workerOlmAPIRequests: $ReadOnlyArray = [ @@ -238,6 +242,14 @@ +operationType: string, }; +export type MigrateBackupSchemaRequestMessage = { + +type: 28, +}; + +export type CopyContentFromBackupDatabaseRequestMessage = { + +type: 29, +}; + export type WorkerRequestMessage = | PingWorkerRequestMessage | InitWorkerRequestMessage @@ -266,7 +278,9 @@ | ResetOutboundP2PMessagesRequestMessage | FetchMessagesRequestMessage | GetInboundP2PMessagesByIDRequestMessage - | GetDMOperationsByTypeRequestMessage; + | GetDMOperationsByTypeRequestMessage + | MigrateBackupSchemaRequestMessage + | CopyContentFromBackupDatabaseRequestMessage; export type WorkerRequestProxyMessage = { +id: number,