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 @@ -75,6 +75,10 @@ currentUserID: ?string, ) => Promise, +getDatabaseVersion: (dbID: DatabaseIdentifier) => Promise, + +getSyncedMetadata: ( + entryName: string, + dbID: DatabaseIdentifier, + ) => Promise, // write operations +removeInboundP2PMessages: (ids: $ReadOnlyArray) => 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 @@ -52,6 +52,7 @@ copyContentFromBackupDatabase: jest.fn(), migrateBackupSchema: jest.fn(), getDatabaseVersion: jest.fn(), + getSyncedMetadata: jest.fn(), }, encryptedNotifUtilsAPI: { generateAESKey: jest.fn(), 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 @@ -197,6 +197,8 @@ virtual std::vector getDMOperationsByType(const std::string &operationType) const = 0; virtual int getDatabaseVersion() const = 0; + virtual std::optional + getSyncedMetadata(const std::string &entryName) 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 @@ -189,6 +189,8 @@ std::vector getDMOperationsByType(const std::string &operationType) const override; int getDatabaseVersion() const override; + std::optional + getSyncedMetadata(const std::string &entryName) 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 @@ -1544,4 +1544,20 @@ return SQLiteUtils::getDatabaseVersion(this->getConnection()); } +std::optional +SQLiteQueryExecutor::getSyncedMetadata(const std::string &entryName) const { + std::string getMetadataByPrimaryKeySQL = + "SELECT * " + "FROM synced_metadata " + "WHERE name = ?;"; + std::unique_ptr entry = + getEntityByPrimaryKey( + this->getConnection(), getMetadataByPrimaryKeySQL, entryName); + + if (entry == nullptr) { + return std::nullopt; + } + return entry->data; +} + } // 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 @@ -257,6 +257,10 @@ virtual jsi::Value getSyncedDatabaseVersion(jsi::Runtime &rt) override; virtual jsi::Value getDatabaseVersion(jsi::Runtime &rt, jsi::String dbID) override; + virtual jsi::Value getSyncedMetadata( + jsi::Runtime &rt, + jsi::String entryName, + 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 @@ -3080,6 +3080,43 @@ }); } +jsi::Value CommCoreModule::getSyncedMetadata( + jsi::Runtime &rt, + jsi::String entryName, + jsi::String dbID) { + std::string entryNameStr = entryName.utf8(rt); + DatabaseIdentifier identifier = stringToDatabaseIdentifier(dbID.utf8(rt)); + return createPromiseAsJSIValue( + rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { + taskType job = [=, &innerRt]() { + std::string error; + std::optional result = std::nullopt; + try { + result = DatabaseManager::getQueryExecutor(identifier) + .getSyncedMetadata(entryNameStr); + } catch (std::system_error &e) { + error = e.what(); + } + + this->jsInvoker_->invokeAsync([&innerRt, error, promise, result]() { + if (error.size()) { + promise->reject(error); + return; + } + if (result.has_value()) { + jsi::String jsiResult = + jsi::String::createFromUtf8(innerRt, result.value()); + promise->resolve(std::move(jsiResult)); + } else { + promise->resolve(jsi::Value::null()); + } + }); + }; + 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 @@ -214,6 +214,9 @@ static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getDatabaseVersion(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->getDatabaseVersion(rt, args[0].asString(rt)); } +static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getSyncedMetadata(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + return static_cast(&turboModule)->getSyncedMetadata(rt, args[0].asString(rt), args[1].asString(rt)); +} static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_markPrekeysAsPublished(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->markPrekeysAsPublished(rt); } @@ -306,6 +309,7 @@ methodMap_["resetOutboundP2PMessagesForDevice"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_resetOutboundP2PMessagesForDevice}; methodMap_["getSyncedDatabaseVersion"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getSyncedDatabaseVersion}; methodMap_["getDatabaseVersion"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getDatabaseVersion}; + methodMap_["getSyncedMetadata"] = MethodMetadata {2, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getSyncedMetadata}; methodMap_["markPrekeysAsPublished"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_markPrekeysAsPublished}; methodMap_["getRelatedMessages"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getRelatedMessages}; methodMap_["searchMessages"] = MethodMetadata {4, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_searchMessages}; 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 @@ -85,6 +85,7 @@ virtual jsi::Value resetOutboundP2PMessagesForDevice(jsi::Runtime &rt, jsi::String deviceID) = 0; virtual jsi::Value getSyncedDatabaseVersion(jsi::Runtime &rt) = 0; virtual jsi::Value getDatabaseVersion(jsi::Runtime &rt, jsi::String dbID) = 0; + virtual jsi::Value getSyncedMetadata(jsi::Runtime &rt, jsi::String entryName, jsi::String dbID) = 0; virtual jsi::Value markPrekeysAsPublished(jsi::Runtime &rt) = 0; virtual jsi::Value getRelatedMessages(jsi::Runtime &rt, jsi::String messageID) = 0; virtual jsi::Value searchMessages(jsi::Runtime &rt, jsi::String query, jsi::String threadID, std::optional timestampCursor, std::optional messageIDCursor) = 0; @@ -634,6 +635,14 @@ return bridging::callFromJs( rt, &T::getDatabaseVersion, jsInvoker_, instance_, std::move(dbID)); } + jsi::Value getSyncedMetadata(jsi::Runtime &rt, jsi::String entryName, jsi::String dbID) override { + static_assert( + bridging::getParameterCount(&T::getSyncedMetadata) == 3, + "Expected getSyncedMetadata(...) to have 3 parameters"); + + return bridging::callFromJs( + rt, &T::getSyncedMetadata, jsInvoker_, instance_, std::move(entryName), std::move(dbID)); + } jsi::Value markPrekeysAsPublished(jsi::Runtime &rt) override { static_assert( bridging::getParameterCount(&T::markPrekeysAsPublished) == 1, 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 @@ -25,6 +25,7 @@ fetchDMOperationsByType: commCoreModule.getDMOperationsByType, getClientDBStore, getDatabaseVersion: commCoreModule.getDatabaseVersion, + getSyncedMetadata: commCoreModule.getSyncedMetadata, // write operations removeInboundP2PMessages: commCoreModule.removeInboundP2PMessages, diff --git a/native/schema/CommCoreModuleSchema.js b/native/schema/CommCoreModuleSchema.js --- a/native/schema/CommCoreModuleSchema.js +++ b/native/schema/CommCoreModuleSchema.js @@ -202,6 +202,8 @@ +getSyncedDatabaseVersion: () => Promise; // This type should be DatabaseIdentifier +getDatabaseVersion: (dbID: string) => Promise; + // `dbID` type should be DatabaseIdentifier + +getSyncedMetadata: (entryName: string, dbID: string) => Promise; +markPrekeysAsPublished: () => Promise; +getRelatedMessages: ( messageID: string, diff --git a/web/cpp/SQLiteQueryExecutorBindings.cpp b/web/cpp/SQLiteQueryExecutorBindings.cpp --- a/web/cpp/SQLiteQueryExecutorBindings.cpp +++ b/web/cpp/SQLiteQueryExecutorBindings.cpp @@ -341,7 +341,8 @@ &SQLiteQueryExecutor::deleteMessageFromSearchIndex) .function("searchMessages", &SQLiteQueryExecutor::searchMessages) .function("fetchMessages", &SQLiteQueryExecutor::fetchMessages) - .function("getDatabaseVersion", &SQLiteQueryExecutor::getDatabaseVersion); + .function("getDatabaseVersion", &SQLiteQueryExecutor::getDatabaseVersion) + .function("getSyncedMetadata", &SQLiteQueryExecutor::getSyncedMetadata); 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 @@ -142,6 +142,19 @@ return data?.databaseVersion ?? 0; }, + async getSyncedMetadata( + entryName: string, + dbID: DatabaseIdentifier, + ): Promise { + const sharedWorker = await getCommSharedWorker(); + const data = await sharedWorker.schedule({ + type: workerRequestMessageTypes.GET_SYNCED_METADATA, + entryName, + dbID, + }); + return data?.syncedMetadata ?? null; + }, + // write operations async removeInboundP2PMessages(ids: $ReadOnlyArray): Promise { const sharedWorker = await getCommSharedWorker(); 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$@ { + expect(queryExecutor?.getSyncedMetadata('test_name_1')).toBe('test_data_1'); + expect( + queryExecutor?.getSyncedMetadata('non_existing_test_name'), + ).toBeNull(); + }); }); diff --git a/web/shared-worker/types/sqlite-query-executor.js b/web/shared-worker/types/sqlite-query-executor.js --- a/web/shared-worker/types/sqlite-query-executor.js +++ b/web/shared-worker/types/sqlite-query-executor.js @@ -218,6 +218,7 @@ ): $ReadOnlyArray; getDatabaseVersion(): number; + getSyncedMetadata(entryName: string): ?string; // method is provided to manually signal that a C++ object // is no longer needed and can be deleted 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 @@ -379,6 +379,28 @@ type: workerResponseMessageTypes.GET_DATABASE_VERSION, databaseVersion: sqliteQueryExecutor.getDatabaseVersion(), }; + } else if (message.type === workerRequestMessageTypes.GET_SYNCED_METADATA) { + if (message.dbID && message.dbID === databaseIdentifier.RESTORED) { + const backupQueryExecutor = getSQLiteQueryExecutor( + databaseIdentifier.RESTORED, + ); + if (!backupQueryExecutor) { + throw new Error( + `Backup not initialized, unable to process request type: ${message.type}`, + ); + } + return { + type: workerResponseMessageTypes.GET_SYNCED_METADATA, + syncedMetadata: backupQueryExecutor.getSyncedMetadata( + message.entryName, + ), + }; + } + + return { + type: workerResponseMessageTypes.GET_SYNCED_METADATA, + syncedMetadata: sqliteQueryExecutor.getSyncedMetadata(message.entryName), + }; } // write operations 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 @@ -56,6 +56,7 @@ MIGRATE_BACKUP_SCHEMA: 28, COPY_CONTENT_FROM_BACKUP_DB: 29, GET_DATABASE_VERSION: 30, + GET_SYNCED_METADATA: 31, }); export const workerWriteRequests: $ReadOnlyArray = [ @@ -259,6 +260,12 @@ +dbID: DatabaseIdentifier, }; +export type GetSyncedMetadataRequestMessage = { + +type: 31, + +entryName: string, + +dbID: DatabaseIdentifier, +}; + export type WorkerRequestMessage = | PingWorkerRequestMessage | InitWorkerRequestMessage @@ -290,7 +297,8 @@ | GetDMOperationsByTypeRequestMessage | MigrateBackupSchemaRequestMessage | CopyContentFromBackupDatabaseRequestMessage - | GetDatabaseVersionRequestMessage; + | GetDatabaseVersionRequestMessage + | GetSyncedMetadataRequestMessage; export type WorkerRequestProxyMessage = { +id: number, @@ -311,6 +319,7 @@ RESET_OUTBOUND_P2P_MESSAGES: 9, DM_OPERATIONS: 10, GET_DATABASE_VERSION: 11, + GET_SYNCED_METADATA: 12, }); export type PongWorkerResponseMessage = { @@ -373,6 +382,11 @@ +databaseVersion: number, }; +export type GetSyncedMetadataResponseMessage = { + +type: 12, + +syncedMetadata: ?string, +}; + export type WorkerResponseMessage = | PongWorkerResponseMessage | ClientStoreResponseMessage @@ -385,7 +399,8 @@ | GetMessagesResponse | ResetOutboundP2PMessagesResponseMessage | DMOperationsResponseMessage - | GetDatabaseVersionResponseMessage; + | GetDatabaseVersionResponseMessage + | GetSyncedMetadataResponseMessage; export type WorkerResponseProxyMessage = { +id?: number,