diff --git a/lib/types/store-ops-types.js b/lib/types/store-ops-types.js index ef9d68c82..df3c0ec06 100644 --- a/lib/types/store-ops-types.js +++ b/lib/types/store-ops-types.js @@ -1,126 +1,129 @@ // @flow import type { AuxUserInfos } from './aux-user-types.js'; import type { CommunityInfos } from './community-types.js'; import type { DraftStoreOperation, ClientDBDraftStoreOperation, ClientDBDraftInfo, } from './draft-types.js'; import type { ThreadHashes } from './integrity-types.js'; import type { KeyserverInfos } from './keyserver-types.js'; import type { ClientDBMessageInfo, ClientDBThreadMessageInfo, } from './message-types.js'; import type { ClientReportCreationRequest } from './report-types.js'; +import type { OutboundP2PMessage } from './sqlite-types.js'; import type { SyncedMetadata } from './synced-metadata-types.js'; import type { ThreadActivityStore } from './thread-activity-types.js'; import type { ClientDBThreadInfo, ThreadStore } from './thread-types.js'; import type { UserInfos } from './user-types.js'; import type { ClientDBAuxUserInfo, ClientDBAuxUserStoreOperation, AuxUserStoreOperation, } from '../ops/aux-user-store-ops.js'; import type { ClientDBCommunityInfo, ClientDBCommunityStoreOperation, CommunityStoreOperation, } from '../ops/community-store-ops.js'; import type { ClientDBIntegrityThreadHash, ClientDBIntegrityStoreOperation, IntegrityStoreOperation, } from '../ops/integrity-store-ops.js'; import type { ClientDBKeyserverInfo, ClientDBKeyserverStoreOperation, KeyserverStoreOperation, } from '../ops/keyserver-store-ops.js'; import type { ClientDBMessageStoreOperation, MessageStoreOperation, } from '../ops/message-store-ops.js'; import type { ReportStoreOperation, ClientDBReport, ClientDBReportStoreOperation, } from '../ops/report-store-ops.js'; import type { ClientDBSyncedMetadataEntry, ClientDBSyncedMetadataStoreOperation, } from '../ops/synced-metadata-store-ops.js'; import type { ThreadActivityStoreOperation, ClientDBThreadActivityEntry, ClientDBThreadActivityStoreOperation, } from '../ops/thread-activity-store-ops.js'; import type { ClientDBThreadStoreOperation, ThreadStoreOperation, } from '../ops/thread-store-ops.js'; import type { ClientDBUserStoreOperation, UserStoreOperation, ClientDBUserInfo, } from '../ops/user-store-ops.js'; export type StoreOperations = { +draftStoreOperations?: $ReadOnlyArray, +threadStoreOperations?: $ReadOnlyArray, +messageStoreOperations?: $ReadOnlyArray, +reportStoreOperations?: $ReadOnlyArray, +userStoreOperations?: $ReadOnlyArray, +keyserverStoreOperations?: $ReadOnlyArray, +communityStoreOperations?: $ReadOnlyArray, +integrityStoreOperations?: $ReadOnlyArray, +syncedMetadataStoreOperations?: $ReadOnlyArray, +auxUserStoreOperations?: $ReadOnlyArray, +threadActivityStoreOperations?: $ReadOnlyArray, + +outboundP2PMessages?: $ReadOnlyArray, }; export type ClientDBStoreOperations = { +draftStoreOperations?: $ReadOnlyArray, +threadStoreOperations?: $ReadOnlyArray, +messageStoreOperations?: $ReadOnlyArray, +reportStoreOperations?: $ReadOnlyArray, +userStoreOperations?: $ReadOnlyArray, +keyserverStoreOperations?: $ReadOnlyArray, +communityStoreOperations?: $ReadOnlyArray, +integrityStoreOperations?: $ReadOnlyArray, +syncedMetadataStoreOperations?: $ReadOnlyArray, +auxUserStoreOperations?: $ReadOnlyArray, +threadActivityStoreOperations?: $ReadOnlyArray, + +outboundP2PMessages?: $ReadOnlyArray, }; export type ClientDBStore = { +messages: $ReadOnlyArray, +drafts: $ReadOnlyArray, +threads: $ReadOnlyArray, +messageStoreThreads: $ReadOnlyArray, +reports: $ReadOnlyArray, +users: $ReadOnlyArray, +keyservers: $ReadOnlyArray, +communities: $ReadOnlyArray, +integrityThreadHashes: $ReadOnlyArray, +syncedMetadata: $ReadOnlyArray, +auxUserInfos: $ReadOnlyArray, +threadActivityEntries: $ReadOnlyArray, }; export type ClientStore = { +currentUserID: ?string, +drafts: $ReadOnlyArray, +messages: ?$ReadOnlyArray, +threadStore: ?ThreadStore, +messageStoreThreads: ?$ReadOnlyArray, +reports: ?$ReadOnlyArray, +users: ?UserInfos, +keyserverInfos: ?KeyserverInfos, +communityInfos: ?CommunityInfos, +threadHashes: ?ThreadHashes, +syncedMetadata: ?SyncedMetadata, +auxUserInfos: ?AuxUserInfos, +threadActivityStore: ?ThreadActivityStore, }; diff --git a/native/cpp/CommonCpp/DatabaseManagers/entities/OutboundP2PMessage.h b/native/cpp/CommonCpp/DatabaseManagers/entities/OutboundP2PMessage.h index 8c25bcf3c..2af9200a2 100644 --- a/native/cpp/CommonCpp/DatabaseManagers/entities/OutboundP2PMessage.h +++ b/native/cpp/CommonCpp/DatabaseManagers/entities/OutboundP2PMessage.h @@ -1,77 +1,94 @@ #pragma once #include #include #include #include "SQLiteDataConverters.h" namespace comm { struct SQLiteOutboundP2PMessage { std::string message_id; std::string device_id; std::string user_id; int64_t timestamp; std::string plaintext; std::string ciphertext; std::string status; static SQLiteOutboundP2PMessage fromSQLResult(sqlite3_stmt *sqlRow, int idx) { return SQLiteOutboundP2PMessage{ getStringFromSQLRow(sqlRow, idx), getStringFromSQLRow(sqlRow, idx + 1), getStringFromSQLRow(sqlRow, idx + 2), getInt64FromSQLRow(sqlRow, idx + 3), getStringFromSQLRow(sqlRow, idx + 4), getStringFromSQLRow(sqlRow, idx + 5), getStringFromSQLRow(sqlRow, idx + 6), }; } int bindToSQL(sqlite3_stmt *sql, int idx) const { bindStringToSQL(message_id, sql, idx); bindStringToSQL(device_id, sql, idx + 1); bindStringToSQL(user_id, sql, idx + 2); bindInt64ToSQL(timestamp, sql, idx + 3); bindStringToSQL(plaintext, sql, idx + 4); bindStringToSQL(ciphertext, sql, idx + 5); return bindStringToSQL(status, sql, idx + 6); } }; struct OutboundP2PMessage { std::string message_id; std::string device_id; std::string user_id; std::string timestamp; std::string plaintext; std::string ciphertext; std::string status; OutboundP2PMessage() = default; + OutboundP2PMessage( + const std::string &message_id, + const std::string &device_id, + const std::string &user_id, + const std::string ×tamp, + const std::string &plaintext, + const std::string &ciphertext, + const std::string &status) + : message_id(message_id), + device_id(device_id), + user_id(user_id), + timestamp(timestamp), + plaintext(plaintext), + ciphertext(ciphertext), + status(status) { + } + OutboundP2PMessage(const SQLiteOutboundP2PMessage &msg) { message_id = msg.message_id; device_id = msg.device_id; user_id = msg.user_id; timestamp = std::to_string(msg.timestamp); plaintext = msg.plaintext; ciphertext = msg.ciphertext; status = msg.status; } SQLiteOutboundP2PMessage toSQLiteOutboundP2PMessage() const { SQLiteOutboundP2PMessage msg; msg.message_id = message_id; msg.device_id = device_id; msg.user_id = user_id; msg.timestamp = std::stoll(timestamp); msg.plaintext = plaintext; msg.ciphertext = ciphertext; msg.status = status; return msg; } }; } // namespace comm diff --git a/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp b/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp index 34ad79190..403c64503 100644 --- a/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp +++ b/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp @@ -1,2425 +1,2469 @@ #include "CommCoreModule.h" #include "../Notifications/BackgroundDataStorage/NotificationsCryptoModule.h" #include "BaseDataStore.h" #include "CommServicesAuthMetadataEmitter.h" #include "DatabaseManager.h" #include "InternalModules/GlobalDBSingleton.h" #include "InternalModules/RustPromiseManager.h" #include "NativeModuleUtils.h" #include "TerminateApp.h" #include #include #include #include #include "JSIRust.h" #include "lib.rs.h" #include #include namespace comm { using namespace facebook::react; jsi::Value CommCoreModule::getDraft(jsi::Runtime &rt, jsi::String key) { std::string keyStr = key.utf8(rt); return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; std::string draftStr; try { draftStr = DatabaseManager::getQueryExecutor().getDraft(keyStr); } catch (std::system_error &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } jsi::String draft = jsi::String::createFromUtf8(innerRt, draftStr); promise->resolve(std::move(draft)); }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } jsi::Value CommCoreModule::updateDraft( jsi::Runtime &rt, jsi::String key, jsi::String text) { std::string keyStr = key.utf8(rt); std::string textStr = text.utf8(rt); return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=]() { std::string error; try { DatabaseManager::getQueryExecutor().updateDraft(keyStr, textStr); } catch (std::system_error &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=]() { if (error.size()) { promise->reject(error); } else { promise->resolve(true); } }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } jsi::Value CommCoreModule::moveDraft( jsi::Runtime &rt, jsi::String oldKey, jsi::String newKey) { std::string oldKeyStr = oldKey.utf8(rt); std::string newKeyStr = newKey.utf8(rt); return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=]() { std::string error; bool result = false; try { result = DatabaseManager::getQueryExecutor().moveDraft( oldKeyStr, newKeyStr); } catch (std::system_error &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=]() { if (error.size()) { promise->reject(error); } else { promise->resolve(result); } }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } jsi::Value CommCoreModule::getClientDBStore(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; std::vector draftsVector; std::vector threadsVector; std::vector>> messagesVector; std::vector messageStoreThreadsVector; std::vector reportStoreVector; std::vector userStoreVector; std::vector keyserverStoreVector; std::vector communityStoreVector; std::vector integrityStoreVector; std::vector syncedMetadataStoreVector; std::vector auxUserStoreVector; std::vector threadActivityStoreVector; try { draftsVector = DatabaseManager::getQueryExecutor().getAllDrafts(); messagesVector = DatabaseManager::getQueryExecutor().getAllMessages(); threadsVector = DatabaseManager::getQueryExecutor().getAllThreads(); messageStoreThreadsVector = DatabaseManager::getQueryExecutor().getAllMessageStoreThreads(); reportStoreVector = DatabaseManager::getQueryExecutor().getAllReports(); userStoreVector = DatabaseManager::getQueryExecutor().getAllUsers(); keyserverStoreVector = DatabaseManager::getQueryExecutor().getAllKeyservers(); communityStoreVector = DatabaseManager::getQueryExecutor().getAllCommunities(); integrityStoreVector = DatabaseManager::getQueryExecutor() .getAllIntegrityThreadHashes(); syncedMetadataStoreVector = DatabaseManager::getQueryExecutor().getAllSyncedMetadata(); auxUserStoreVector = DatabaseManager::getQueryExecutor().getAllAuxUserInfos(); threadActivityStoreVector = DatabaseManager::getQueryExecutor() .getAllThreadActivityEntries(); } catch (std::system_error &e) { error = e.what(); } auto draftsVectorPtr = std::make_shared>(std::move(draftsVector)); auto messagesVectorPtr = std::make_shared< std::vector>>>( std::move(messagesVector)); auto threadsVectorPtr = std::make_shared>(std::move(threadsVector)); auto messageStoreThreadsVectorPtr = std::make_shared>( std::move(messageStoreThreadsVector)); auto reportStoreVectorPtr = std::make_shared>( std::move(reportStoreVector)); auto userStoreVectorPtr = std::make_shared>( std::move(userStoreVector)); auto keyserveStoreVectorPtr = std::make_shared>( std::move(keyserverStoreVector)); auto communityStoreVectorPtr = std::make_shared>( std::move(communityStoreVector)); auto integrityStoreVectorPtr = std::make_shared>( std::move(integrityStoreVector)); auto syncedMetadataStoreVectorPtr = std::make_shared>( std::move(syncedMetadataStoreVector)); auto auxUserStoreVectorPtr = std::make_shared>( std::move(auxUserStoreVector)); auto threadActivityStoreVectorPtr = std::make_shared>( std::move(threadActivityStoreVector)); this->jsInvoker_->invokeAsync([&innerRt, draftsVectorPtr, messagesVectorPtr, threadsVectorPtr, messageStoreThreadsVectorPtr, reportStoreVectorPtr, userStoreVectorPtr, keyserveStoreVectorPtr, communityStoreVectorPtr, integrityStoreVectorPtr, syncedMetadataStoreVectorPtr, auxUserStoreVectorPtr, threadActivityStoreVectorPtr, error, promise, draftStore = this->draftStore, threadStore = this->threadStore, messageStore = this->messageStore, reportStore = this->reportStore, userStore = this->userStore, keyserverStore = this->keyserverStore, communityStore = this->communityStore, integrityStore = this->integrityStore, syncedMetadataStore = this->syncedMetadataStore, auxUserStore = this->auxUserStore, threadActivityStore = this->threadActivityStore]() { if (error.size()) { promise->reject(error); return; } jsi::Array jsiDrafts = draftStore.parseDBDataStore(innerRt, draftsVectorPtr); jsi::Array jsiMessages = messageStore.parseDBDataStore(innerRt, messagesVectorPtr); jsi::Array jsiThreads = threadStore.parseDBDataStore(innerRt, threadsVectorPtr); jsi::Array jsiMessageStoreThreads = messageStore.parseDBMessageStoreThreads( innerRt, messageStoreThreadsVectorPtr); jsi::Array jsiReportStore = reportStore.parseDBDataStore(innerRt, reportStoreVectorPtr); jsi::Array jsiUserStore = userStore.parseDBDataStore(innerRt, userStoreVectorPtr); jsi::Array jsiKeyserverStore = keyserverStore.parseDBDataStore( innerRt, keyserveStoreVectorPtr); jsi::Array jsiCommunityStore = communityStore.parseDBDataStore( innerRt, communityStoreVectorPtr); jsi::Array jsiIntegrityStore = integrityStore.parseDBDataStore( innerRt, integrityStoreVectorPtr); jsi::Array jsiSyncedMetadataStore = syncedMetadataStore.parseDBDataStore( innerRt, syncedMetadataStoreVectorPtr); jsi::Array jsiAuxUserStore = auxUserStore.parseDBDataStore(innerRt, auxUserStoreVectorPtr); jsi::Array jsiThreadActivityStore = threadActivityStore.parseDBDataStore( innerRt, threadActivityStoreVectorPtr); auto jsiClientDBStore = jsi::Object(innerRt); jsiClientDBStore.setProperty(innerRt, "messages", jsiMessages); jsiClientDBStore.setProperty(innerRt, "threads", jsiThreads); jsiClientDBStore.setProperty(innerRt, "drafts", jsiDrafts); jsiClientDBStore.setProperty( innerRt, "messageStoreThreads", jsiMessageStoreThreads); jsiClientDBStore.setProperty(innerRt, "reports", jsiReportStore); jsiClientDBStore.setProperty(innerRt, "users", jsiUserStore); jsiClientDBStore.setProperty( innerRt, "keyservers", jsiKeyserverStore); jsiClientDBStore.setProperty( innerRt, "communities", jsiCommunityStore); jsiClientDBStore.setProperty( innerRt, "integrityThreadHashes", jsiIntegrityStore); jsiClientDBStore.setProperty( innerRt, "syncedMetadata", jsiSyncedMetadataStore); jsiClientDBStore.setProperty( innerRt, "auxUserInfos", jsiAuxUserStore); jsiClientDBStore.setProperty( innerRt, "threadActivityEntries", jsiThreadActivityStore); promise->resolve(std::move(jsiClientDBStore)); }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } jsi::Value CommCoreModule::removeAllDrafts(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=]() { std::string error; try { DatabaseManager::getQueryExecutor().removeAllDrafts(); } catch (std::system_error &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=]() { if (error.size()) { promise->reject(error); return; } promise->resolve(jsi::Value::undefined()); }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } jsi::Array CommCoreModule::getAllMessagesSync(jsi::Runtime &rt) { auto messagesVector = NativeModuleUtils::runSyncOrThrowJSError< std::vector>>>(rt, []() { return DatabaseManager::getQueryExecutor().getAllMessages(); }); auto messagesVectorPtr = std::make_shared>>>( std::move(messagesVector)); jsi::Array jsiMessages = this->messageStore.parseDBDataStore(rt, messagesVectorPtr); return jsiMessages; } void CommCoreModule::processMessageStoreOperationsSync( jsi::Runtime &rt, jsi::Array operations) { return this->messageStore.processStoreOperationsSync( rt, std::move(operations)); } jsi::Array CommCoreModule::getAllThreadsSync(jsi::Runtime &rt) { auto threadsVector = NativeModuleUtils::runSyncOrThrowJSError>(rt, []() { return DatabaseManager::getQueryExecutor().getAllThreads(); }); auto threadsVectorPtr = std::make_shared>(std::move(threadsVector)); jsi::Array jsiThreads = this->threadStore.parseDBDataStore(rt, threadsVectorPtr); return jsiThreads; } void CommCoreModule::processThreadStoreOperationsSync( jsi::Runtime &rt, jsi::Array operations) { this->threadStore.processStoreOperationsSync(rt, std::move(operations)); } void CommCoreModule::processReportStoreOperationsSync( jsi::Runtime &rt, jsi::Array operations) { this->reportStore.processStoreOperationsSync(rt, std::move(operations)); } template void CommCoreModule::appendDBStoreOps( jsi::Runtime &rt, jsi::Object &operations, const char *key, T &store, std::shared_ptr>> &destination) { auto opsObject = operations.getProperty(rt, key); if (opsObject.isObject()) { auto ops = store.createOperations(rt, opsObject.asObject(rt).asArray(rt)); std::move( std::make_move_iterator(ops.begin()), std::make_move_iterator(ops.end()), std::back_inserter(*destination)); } } jsi::Value CommCoreModule::processDBStoreOperations( jsi::Runtime &rt, jsi::Object operations) { std::string createOperationsError; auto storeOpsPtr = std::make_shared>>(); try { this->appendDBStoreOps( rt, operations, "draftStoreOperations", this->draftStore, storeOpsPtr); this->appendDBStoreOps( rt, operations, "threadStoreOperations", this->threadStore, storeOpsPtr); this->appendDBStoreOps( rt, operations, "messageStoreOperations", this->messageStore, storeOpsPtr); this->appendDBStoreOps( rt, operations, "reportStoreOperations", this->reportStore, storeOpsPtr); this->appendDBStoreOps( rt, operations, "userStoreOperations", this->userStore, storeOpsPtr); this->appendDBStoreOps( rt, operations, "keyserverStoreOperations", this->keyserverStore, storeOpsPtr); this->appendDBStoreOps( rt, operations, "communityStoreOperations", this->communityStore, storeOpsPtr); this->appendDBStoreOps( rt, operations, "integrityStoreOperations", this->integrityStore, storeOpsPtr); this->appendDBStoreOps( rt, operations, "syncedMetadataStoreOperations", this->syncedMetadataStore, storeOpsPtr); this->appendDBStoreOps( rt, operations, "auxUserStoreOperations", this->auxUserStore, storeOpsPtr); this->appendDBStoreOps( rt, operations, "threadActivityStoreOperations", this->threadActivityStore, storeOpsPtr); } catch (std::runtime_error &e) { createOperationsError = e.what(); } + std::vector messages; + try { + auto messagesJSIObj = operations.getProperty(rt, "outboundP2PMessages"); + + if (messagesJSIObj.isObject()) { + auto messagesJSI = messagesJSIObj.asObject(rt).asArray(rt); + for (size_t idx = 0; idx < messagesJSI.size(rt); idx++) { + jsi::Object msgObj = messagesJSI.getValueAtIndex(rt, idx).asObject(rt); + + std::string messageID = + msgObj.getProperty(rt, "messageID").asString(rt).utf8(rt); + std::string deviceID = + msgObj.getProperty(rt, "deviceID").asString(rt).utf8(rt); + std::string userID = + msgObj.getProperty(rt, "userID").asString(rt).utf8(rt); + std::string timestamp = + msgObj.getProperty(rt, "timestamp").asString(rt).utf8(rt); + std::string plaintext = + msgObj.getProperty(rt, "plaintext").asString(rt).utf8(rt); + std::string ciphertext = + msgObj.getProperty(rt, "ciphertext").asString(rt).utf8(rt); + std::string status = + msgObj.getProperty(rt, "status").asString(rt).utf8(rt); + + OutboundP2PMessage outboundMessage{ + messageID, + deviceID, + userID, + timestamp, + plaintext, + ciphertext, + status}; + messages.push_back(outboundMessage); + } + } + + } catch (std::runtime_error &e) { + createOperationsError = e.what(); + } + return facebook::react::createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=]() { std::string error = createOperationsError; if (!error.size()) { try { DatabaseManager::getQueryExecutor().beginTransaction(); for (const auto &operation : *storeOpsPtr) { operation->execute(); } + if (messages.size() > 0) { + DatabaseManager::getQueryExecutor().addOutboundP2PMessages( + messages); + } DatabaseManager::getQueryExecutor().captureBackupLogs(); DatabaseManager::getQueryExecutor().commitTransaction(); } catch (std::system_error &e) { error = e.what(); DatabaseManager::getQueryExecutor().rollbackTransaction(); } } if (!error.size()) { ::triggerBackupFileUpload(); } this->jsInvoker_->invokeAsync([=]() { if (error.size()) { promise->reject(error); } else { promise->resolve(jsi::Value::undefined()); } }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } void CommCoreModule::terminate(jsi::Runtime &rt) { TerminateApp::terminate(); } const std::string getAccountDataKey(const std::string secureStoreAccountDataKey) { folly::Optional storedSecretKey = CommSecureStore::get(secureStoreAccountDataKey); if (!storedSecretKey.hasValue()) { storedSecretKey = crypto::Tools::generateRandomString(64); CommSecureStore::set(secureStoreAccountDataKey, storedSecretKey.value()); } return storedSecretKey.value(); } void CommCoreModule::persistCryptoModules( bool persistContentModule, bool persistNotifsModule) { std::string storedSecretKey = getAccountDataKey(secureStoreAccountDataKey); if (!persistContentModule && !persistNotifsModule) { return; } crypto::Persist newContentPersist; if (persistContentModule) { newContentPersist = this->contentCryptoModule->storeAsB64(storedSecretKey); } crypto::Persist newNotifsPersist; if (persistNotifsModule) { newNotifsPersist = this->notifsCryptoModule->storeAsB64(storedSecretKey); } std::promise persistencePromise; std::future persistenceFuture = persistencePromise.get_future(); GlobalDBSingleton::instance.scheduleOrRunCancellable( [=, &persistencePromise]() { try { DatabaseManager::getQueryExecutor().beginTransaction(); if (persistContentModule) { DatabaseManager::getQueryExecutor().storeOlmPersistData( DatabaseManager::getQueryExecutor().getContentAccountID(), newContentPersist); } if (persistNotifsModule) { DatabaseManager::getQueryExecutor().storeOlmPersistData( DatabaseManager::getQueryExecutor().getNotifsAccountID(), newNotifsPersist); } DatabaseManager::getQueryExecutor().commitTransaction(); persistencePromise.set_value(); } catch (std::system_error &e) { DatabaseManager::getQueryExecutor().rollbackTransaction(); persistencePromise.set_exception(std::make_exception_ptr(e)); } }); persistenceFuture.get(); } jsi::Value CommCoreModule::initializeCryptoAccount(jsi::Runtime &rt) { folly::Optional storedSecretKey = CommSecureStore::get(this->secureStoreAccountDataKey); if (!storedSecretKey.hasValue()) { storedSecretKey = crypto::Tools::generateRandomString(64); CommSecureStore::set( this->secureStoreAccountDataKey, storedSecretKey.value()); } return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=]() { crypto::Persist contentPersist; crypto::Persist notifsPersist; std::string error; try { std::optional contentAccountData = DatabaseManager::getQueryExecutor().getOlmPersistAccountData( DatabaseManager::getQueryExecutor().getContentAccountID()); if (contentAccountData.has_value()) { contentPersist.account = crypto::OlmBuffer( contentAccountData->begin(), contentAccountData->end()); // handle sessions data std::vector sessionsData = DatabaseManager::getQueryExecutor() .getOlmPersistSessionsData(); for (OlmPersistSession &sessionsDataItem : sessionsData) { crypto::OlmBuffer sessionDataBuffer( sessionsDataItem.session_data.begin(), sessionsDataItem.session_data.end()); crypto::SessionPersist sessionPersist{ sessionDataBuffer, sessionsDataItem.version}; contentPersist.sessions.insert(std::make_pair( sessionsDataItem.target_device_id, sessionPersist)); } } std::optional notifsAccountData = DatabaseManager::getQueryExecutor().getOlmPersistAccountData( DatabaseManager::getQueryExecutor().getNotifsAccountID()); if (notifsAccountData.has_value()) { notifsPersist.account = crypto::OlmBuffer( notifsAccountData->begin(), notifsAccountData->end()); } } catch (std::system_error &e) { error = e.what(); } this->cryptoThread->scheduleTask([=]() { std::string error; this->contentCryptoModule.reset(new crypto::CryptoModule( this->publicCryptoAccountID, storedSecretKey.value(), contentPersist)); this->notifsCryptoModule.reset(new crypto::CryptoModule( this->notifsCryptoAccountID, storedSecretKey.value(), notifsPersist)); try { this->persistCryptoModules( contentPersist.isEmpty(), notifsPersist.isEmpty()); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=]() { if (error.size()) { promise->reject(error); return; } }); this->jsInvoker_->invokeAsync( [=]() { promise->resolve(jsi::Value::undefined()); }); }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } jsi::Value CommCoreModule::getUserPublicKey(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; std::string primaryKeysResult; std::string notificationsKeysResult; if (this->contentCryptoModule == nullptr || this->notifsCryptoModule == nullptr) { error = "user has not been initialized"; } else { primaryKeysResult = this->contentCryptoModule->getIdentityKeys(); notificationsKeysResult = this->notifsCryptoModule->getIdentityKeys(); } std::string notificationsCurve25519Cpp, notificationsEd25519Cpp, blobPayloadCpp, signatureCpp, primaryCurve25519Cpp, primaryEd25519Cpp; if (!error.size()) { folly::dynamic parsedPrimary; try { parsedPrimary = folly::parseJson(primaryKeysResult); } catch (const folly::json::parse_error &e) { error = "parsing identity keys failed with: " + std::string(e.what()); } if (!error.size()) { primaryCurve25519Cpp = parsedPrimary["curve25519"].asString(); primaryEd25519Cpp = parsedPrimary["ed25519"].asString(); folly::dynamic parsedNotifications; try { parsedNotifications = folly::parseJson(notificationsKeysResult); } catch (const folly::json::parse_error &e) { error = "parsing notifications keys failed with: " + std::string(e.what()); } if (!error.size()) { notificationsCurve25519Cpp = parsedNotifications["curve25519"].asString(); notificationsEd25519Cpp = parsedNotifications["ed25519"].asString(); folly::dynamic blobPayloadJSON = folly::dynamic::object( "primaryIdentityPublicKeys", folly::dynamic::object("ed25519", primaryEd25519Cpp)( "curve25519", primaryCurve25519Cpp))( "notificationIdentityPublicKeys", folly::dynamic::object("ed25519", notificationsEd25519Cpp)( "curve25519", notificationsCurve25519Cpp)); blobPayloadCpp = folly::toJson(blobPayloadJSON); signatureCpp = this->contentCryptoModule->signMessage(blobPayloadCpp); } } } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } auto primaryCurve25519{ jsi::String::createFromUtf8(innerRt, primaryCurve25519Cpp)}; auto primaryEd25519{ jsi::String::createFromUtf8(innerRt, primaryEd25519Cpp)}; auto jsiPrimaryIdentityPublicKeys = jsi::Object(innerRt); jsiPrimaryIdentityPublicKeys.setProperty( innerRt, "ed25519", primaryEd25519); jsiPrimaryIdentityPublicKeys.setProperty( innerRt, "curve25519", primaryCurve25519); auto notificationsCurve25519{jsi::String::createFromUtf8( innerRt, notificationsCurve25519Cpp)}; auto notificationsEd25519{ jsi::String::createFromUtf8(innerRt, notificationsEd25519Cpp)}; auto jsiNotificationIdentityPublicKeys = jsi::Object(innerRt); jsiNotificationIdentityPublicKeys.setProperty( innerRt, "ed25519", notificationsEd25519); jsiNotificationIdentityPublicKeys.setProperty( innerRt, "curve25519", notificationsCurve25519); auto blobPayload{ jsi::String::createFromUtf8(innerRt, blobPayloadCpp)}; auto signature{jsi::String::createFromUtf8(innerRt, signatureCpp)}; auto jsiClientPublicKeys = jsi::Object(innerRt); jsiClientPublicKeys.setProperty( innerRt, "primaryIdentityPublicKeys", jsiPrimaryIdentityPublicKeys); jsiClientPublicKeys.setProperty( innerRt, "notificationIdentityPublicKeys", jsiNotificationIdentityPublicKeys); jsiClientPublicKeys.setProperty( innerRt, "blobPayload", blobPayload); jsiClientPublicKeys.setProperty(innerRt, "signature", signature); promise->resolve(std::move(jsiClientPublicKeys)); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Object parseOLMOneTimeKeys(jsi::Runtime &rt, std::string oneTimeKeysBlob) { folly::dynamic parsedOneTimeKeys = folly::parseJson(oneTimeKeysBlob); auto jsiOneTimeKeysInner = jsi::Object(rt); for (auto &kvPair : parsedOneTimeKeys["curve25519"].items()) { jsiOneTimeKeysInner.setProperty( rt, kvPair.first.asString().c_str(), jsi::String::createFromUtf8(rt, kvPair.second.asString())); } auto jsiOneTimeKeys = jsi::Object(rt); jsiOneTimeKeys.setProperty(rt, "curve25519", jsiOneTimeKeysInner); return jsiOneTimeKeys; } std::string parseOLMPrekey(std::string prekeyBlob) { folly::dynamic parsedPrekey; try { parsedPrekey = folly::parseJson(prekeyBlob); } catch (const folly::json::parse_error &e) { throw std::runtime_error( "parsing prekey failed with: " + std::string(e.what())); } folly::dynamic innerObject = parsedPrekey["curve25519"]; if (!innerObject.isObject()) { throw std::runtime_error("parsing prekey failed: inner object malformed"); } if (innerObject.values().begin() == innerObject.values().end()) { throw std::runtime_error("parsing prekey failed: prekey missing"); } return parsedPrekey["curve25519"].values().begin()->asString(); } jsi::Object parseOneTimeKeysResult( jsi::Runtime &rt, std::string contentOneTimeKeysBlob, std::string notifOneTimeKeysBlob) { auto contentOneTimeKeys = parseOLMOneTimeKeys(rt, contentOneTimeKeysBlob); auto notifOneTimeKeys = parseOLMOneTimeKeys(rt, notifOneTimeKeysBlob); auto jsiOneTimeKeysResult = jsi::Object(rt); jsiOneTimeKeysResult.setProperty( rt, "contentOneTimeKeys", contentOneTimeKeys); jsiOneTimeKeysResult.setProperty( rt, "notificationsOneTimeKeys", notifOneTimeKeys); return jsiOneTimeKeysResult; } jsi::Value CommCoreModule::getOneTimeKeys(jsi::Runtime &rt, double oneTimeKeysAmount) { return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; std::string contentResult; std::string notifResult; if (this->contentCryptoModule == nullptr || this->notifsCryptoModule == nullptr) { this->jsInvoker_->invokeAsync([=, &innerRt]() { promise->reject("user has not been initialized"); }); return; } try { contentResult = this->contentCryptoModule->getOneTimeKeysForPublishing( oneTimeKeysAmount); notifResult = this->notifsCryptoModule->getOneTimeKeysForPublishing( oneTimeKeysAmount); this->persistCryptoModules(true, true); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } promise->resolve( parseOneTimeKeysResult(innerRt, contentResult, notifResult)); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::validateAndUploadPrekeys( jsi::Runtime &rt, jsi::String authUserID, jsi::String authDeviceID, jsi::String authAccessToken) { auto authUserIDRust = jsiStringToRustString(authUserID, rt); auto authDeviceIDRust = jsiStringToRustString(authDeviceID, rt); auto authAccessTokenRust = jsiStringToRustString(authAccessToken, rt); return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; std::optional maybeContentPrekeyToUpload; std::optional maybeNotifsPrekeyToUpload; if (this->contentCryptoModule == nullptr || this->notifsCryptoModule == nullptr) { this->jsInvoker_->invokeAsync([=, &innerRt]() { promise->reject("user has not been initialized"); }); return; } try { maybeContentPrekeyToUpload = this->contentCryptoModule->validatePrekey(); maybeNotifsPrekeyToUpload = this->notifsCryptoModule->validatePrekey(); this->persistCryptoModules(true, true); if (!maybeContentPrekeyToUpload.has_value()) { maybeContentPrekeyToUpload = this->contentCryptoModule->getUnpublishedPrekey(); } if (!maybeNotifsPrekeyToUpload.has_value()) { maybeNotifsPrekeyToUpload = this->notifsCryptoModule->getUnpublishedPrekey(); } } catch (const std::exception &e) { error = e.what(); } if (error.size()) { this->jsInvoker_->invokeAsync( [=, &innerRt]() { promise->reject(error); }); return; } if (!maybeContentPrekeyToUpload.has_value() && !maybeNotifsPrekeyToUpload.has_value()) { this->jsInvoker_->invokeAsync( [=]() { promise->resolve(jsi::Value::undefined()); }); return; } std::string contentPrekeyToUpload; if (maybeContentPrekeyToUpload.has_value()) { contentPrekeyToUpload = maybeContentPrekeyToUpload.value(); } else { contentPrekeyToUpload = this->contentCryptoModule->getPrekey(); } std::string notifsPrekeyToUpload; if (maybeNotifsPrekeyToUpload.has_value()) { notifsPrekeyToUpload = maybeNotifsPrekeyToUpload.value(); } else { notifsPrekeyToUpload = this->notifsCryptoModule->getPrekey(); } std::string prekeyUploadError; try { std::string contentPrekeySignature = this->contentCryptoModule->getPrekeySignature(); std::string notifsPrekeySignature = this->notifsCryptoModule->getPrekeySignature(); try { std::promise prekeyPromise; std::future prekeyFuture = prekeyPromise.get_future(); RustPromiseManager::CPPPromiseInfo promiseInfo = { std::move(prekeyPromise)}; auto currentID = RustPromiseManager::instance.addPromise( std::move(promiseInfo)); auto contentPrekeyToUploadRust = rust::String(parseOLMPrekey(contentPrekeyToUpload)); auto prekeySignatureRust = rust::string(contentPrekeySignature); auto notifsPrekeyToUploadRust = rust::String(parseOLMPrekey(notifsPrekeyToUpload)); auto notificationsPrekeySignatureRust = rust::string(notifsPrekeySignature); ::identityRefreshUserPrekeys( authUserIDRust, authDeviceIDRust, authAccessTokenRust, contentPrekeyToUploadRust, prekeySignatureRust, notifsPrekeyToUploadRust, notificationsPrekeySignatureRust, currentID); prekeyFuture.get(); } catch (const std::exception &e) { prekeyUploadError = e.what(); } if (!prekeyUploadError.size()) { this->contentCryptoModule->markPrekeyAsPublished(); this->notifsCryptoModule->markPrekeyAsPublished(); this->persistCryptoModules(true, true); } } catch (std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=]() { if (error.size()) { promise->reject(error); return; } if (prekeyUploadError.size()) { promise->reject(prekeyUploadError); return; } promise->resolve(jsi::Value::undefined()); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::validateAndGetPrekeys(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; std::string contentPrekey, notifPrekey, contentPrekeySignature, notifPrekeySignature; std::optional contentPrekeyBlob; std::optional notifPrekeyBlob; if (this->contentCryptoModule == nullptr || this->notifsCryptoModule == nullptr) { this->jsInvoker_->invokeAsync([=, &innerRt]() { promise->reject("user has not been initialized"); }); return; } try { contentPrekeyBlob = this->contentCryptoModule->validatePrekey(); if (!contentPrekeyBlob) { contentPrekeyBlob = this->contentCryptoModule->getUnpublishedPrekey(); } if (!contentPrekeyBlob) { contentPrekeyBlob = this->contentCryptoModule->getPrekey(); } notifPrekeyBlob = this->notifsCryptoModule->validatePrekey(); if (!notifPrekeyBlob) { notifPrekeyBlob = this->notifsCryptoModule->getUnpublishedPrekey(); } if (!notifPrekeyBlob) { notifPrekeyBlob = this->notifsCryptoModule->getPrekey(); } this->persistCryptoModules(true, true); contentPrekeySignature = this->contentCryptoModule->getPrekeySignature(); notifPrekeySignature = this->notifsCryptoModule->getPrekeySignature(); contentPrekey = parseOLMPrekey(contentPrekeyBlob.value()); notifPrekey = parseOLMPrekey(notifPrekeyBlob.value()); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } auto contentPrekeyJSI = jsi::String::createFromUtf8(innerRt, contentPrekey); auto contentPrekeySignatureJSI = jsi::String::createFromUtf8(innerRt, contentPrekeySignature); auto notifPrekeyJSI = jsi::String::createFromUtf8(innerRt, notifPrekey); auto notifPrekeySignatureJSI = jsi::String::createFromUtf8(innerRt, notifPrekeySignature); auto signedPrekeysJSI = jsi::Object(innerRt); signedPrekeysJSI.setProperty( innerRt, "contentPrekey", contentPrekeyJSI); signedPrekeysJSI.setProperty( innerRt, "contentPrekeySignature", contentPrekeySignatureJSI); signedPrekeysJSI.setProperty( innerRt, "notifPrekey", notifPrekeyJSI); signedPrekeysJSI.setProperty( innerRt, "notifPrekeySignature", notifPrekeySignatureJSI); promise->resolve(std::move(signedPrekeysJSI)); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::initializeNotificationsSession( jsi::Runtime &rt, jsi::String identityKeys, jsi::String prekey, jsi::String prekeySignature, jsi::String oneTimeKey, jsi::String keyserverID) { auto identityKeysCpp{identityKeys.utf8(rt)}; auto prekeyCpp{prekey.utf8(rt)}; auto prekeySignatureCpp{prekeySignature.utf8(rt)}; auto oneTimeKeyCpp{oneTimeKey.utf8(rt)}; auto keyserverIDCpp{keyserverID.utf8(rt)}; return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; crypto::EncryptedData result; try { this->notifsCryptoModule->initializeOutboundForSendingSession( keyserverIDCpp, std::vector( identityKeysCpp.begin(), identityKeysCpp.end()), std::vector(prekeyCpp.begin(), prekeyCpp.end()), std::vector( prekeySignatureCpp.begin(), prekeySignatureCpp.end()), std::vector( oneTimeKeyCpp.begin(), oneTimeKeyCpp.end())); result = this->notifsCryptoModule->encrypt( keyserverIDCpp, NotificationsCryptoModule::initialEncryptedMessageContent); std::shared_ptr keyserverNotificationsSession = this->notifsCryptoModule->getSessionByDeviceId(keyserverIDCpp); NotificationsCryptoModule::persistNotificationsSession( keyserverIDCpp, keyserverNotificationsSession); this->notifsCryptoModule->removeSessionByDeviceId(keyserverIDCpp); this->persistCryptoModules(false, true); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } promise->resolve(jsi::String::createFromUtf8( innerRt, std::string{result.message.begin(), result.message.end()})); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::isNotificationsSessionInitialized(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; bool result; try { result = NotificationsCryptoModule::isNotificationsSessionInitialized( "Comm"); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } promise->resolve(result); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::updateKeyserverDataInNotifStorage( jsi::Runtime &rt, jsi::Array keyserversData) { std::vector> keyserversDataCpp; for (auto idx = 0; idx < keyserversData.size(rt); idx++) { auto data = keyserversData.getValueAtIndex(rt, idx).asObject(rt); std::string keyserverID = data.getProperty(rt, "id").asString(rt).utf8(rt); std::string keyserverUnreadCountKey = "KEYSERVER." + keyserverID + ".UNREAD_COUNT"; int unreadCount = data.getProperty(rt, "unreadCount").asNumber(); keyserversDataCpp.push_back({keyserverUnreadCountKey, unreadCount}); } return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { std::string error; try { for (const auto &keyserverData : keyserversDataCpp) { CommMMKV::setInt(keyserverData.first, keyserverData.second); } } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } promise->resolve(jsi::Value::undefined()); }); }); } jsi::Value CommCoreModule::removeKeyserverDataFromNotifStorage( jsi::Runtime &rt, jsi::Array keyserverIDsToDelete) { std::vector keyserverIDsToDeleteCpp{}; for (auto idx = 0; idx < keyserverIDsToDelete.size(rt); idx++) { std::string keyserverID = keyserverIDsToDelete.getValueAtIndex(rt, idx).asString(rt).utf8(rt); std::string keyserverUnreadCountKey = "KEYSERVER." + keyserverID + ".UNREAD_COUNT"; keyserverIDsToDeleteCpp.push_back(keyserverUnreadCountKey); } return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { std::string error; try { CommMMKV::removeKeys(keyserverIDsToDeleteCpp); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } promise->resolve(jsi::Value::undefined()); }); }); } jsi::Value CommCoreModule::getKeyserverDataFromNotifStorage( jsi::Runtime &rt, jsi::Array keyserverIDs) { std::vector keyserverIDsCpp{}; for (auto idx = 0; idx < keyserverIDs.size(rt); idx++) { std::string keyserverID = keyserverIDs.getValueAtIndex(rt, idx).asString(rt).utf8(rt); keyserverIDsCpp.push_back(keyserverID); } return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { std::string error; std::vector> keyserversDataVector{}; try { for (const auto &keyserverID : keyserverIDsCpp) { std::string keyserverUnreadCountKey = "KEYSERVER." + keyserverID + ".UNREAD_COUNT"; std::optional unreadCount = CommMMKV::getInt(keyserverUnreadCountKey, -1); if (!unreadCount.has_value()) { continue; } keyserversDataVector.push_back({keyserverID, unreadCount.value()}); } } catch (const std::exception &e) { error = e.what(); } auto keyserversDataVectorPtr = std::make_shared>>( std::move(keyserversDataVector)); this->jsInvoker_->invokeAsync( [&innerRt, keyserversDataVectorPtr, error, promise]() { if (error.size()) { promise->reject(error); return; } size_t numKeyserversData = keyserversDataVectorPtr->size(); jsi::Array jsiKeyserversData = jsi::Array(innerRt, numKeyserversData); size_t writeIdx = 0; for (const auto &keyserverData : *keyserversDataVectorPtr) { jsi::Object jsiKeyserverData = jsi::Object(innerRt); jsiKeyserverData.setProperty( innerRt, "id", keyserverData.first); jsiKeyserverData.setProperty( innerRt, "unreadCount", keyserverData.second); jsiKeyserversData.setValueAtIndex( innerRt, writeIdx++, jsiKeyserverData); } promise->resolve(std::move(jsiKeyserversData)); }); }); } jsi::Value CommCoreModule::initializeContentOutboundSession( jsi::Runtime &rt, jsi::String identityKeys, jsi::String prekey, jsi::String prekeySignature, jsi::String oneTimeKey, jsi::String deviceID) { auto identityKeysCpp{identityKeys.utf8(rt)}; auto prekeyCpp{prekey.utf8(rt)}; auto prekeySignatureCpp{prekeySignature.utf8(rt)}; auto oneTimeKeyCpp{oneTimeKey.utf8(rt)}; auto deviceIDCpp{deviceID.utf8(rt)}; return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; crypto::EncryptedData initialEncryptedData; int sessionVersion; try { sessionVersion = this->contentCryptoModule->initializeOutboundForSendingSession( deviceIDCpp, std::vector( identityKeysCpp.begin(), identityKeysCpp.end()), std::vector(prekeyCpp.begin(), prekeyCpp.end()), std::vector( prekeySignatureCpp.begin(), prekeySignatureCpp.end()), std::vector( oneTimeKeyCpp.begin(), oneTimeKeyCpp.end())); const std::string initMessage = "{\"type\": \"init\"}"; initialEncryptedData = contentCryptoModule->encrypt(deviceIDCpp, initMessage); this->persistCryptoModules(true, false); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } auto initialEncryptedDataJSI = jsi::Object(innerRt); auto message = std::string{ initialEncryptedData.message.begin(), initialEncryptedData.message.end()}; auto messageJSI = jsi::String::createFromUtf8(innerRt, message); initialEncryptedDataJSI.setProperty(innerRt, "message", messageJSI); initialEncryptedDataJSI.setProperty( innerRt, "messageType", static_cast(initialEncryptedData.messageType)); auto outboundSessionCreationResultJSI = jsi::Object(innerRt); outboundSessionCreationResultJSI.setProperty( innerRt, "encryptedData", initialEncryptedDataJSI); outboundSessionCreationResultJSI.setProperty( innerRt, "sessionVersion", sessionVersion); promise->resolve(std::move(outboundSessionCreationResultJSI)); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::initializeContentInboundSession( jsi::Runtime &rt, jsi::String identityKeys, jsi::Object encryptedDataJSI, jsi::String deviceID, double sessionVersion, bool overwrite) { auto identityKeysCpp{identityKeys.utf8(rt)}; size_t messageType = std::lround(encryptedDataJSI.getProperty(rt, "messageType").asNumber()); std::string encryptedMessageCpp = encryptedDataJSI.getProperty(rt, "message").asString(rt).utf8(rt); auto deviceIDCpp{deviceID.utf8(rt)}; return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; std::string decryptedMessage; try { this->contentCryptoModule->initializeInboundForReceivingSession( deviceIDCpp, std::vector( encryptedMessageCpp.begin(), encryptedMessageCpp.end()), std::vector( identityKeysCpp.begin(), identityKeysCpp.end()), static_cast(sessionVersion), overwrite); crypto::EncryptedData encryptedData{ std::vector( encryptedMessageCpp.begin(), encryptedMessageCpp.end()), messageType}; decryptedMessage = this->contentCryptoModule->decrypt(deviceIDCpp, encryptedData); this->persistCryptoModules(true, false); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } promise->resolve( jsi::String::createFromUtf8(innerRt, decryptedMessage)); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::encrypt( jsi::Runtime &rt, jsi::String message, jsi::String deviceID) { auto messageCpp{message.utf8(rt)}; auto deviceIDCpp{deviceID.utf8(rt)}; return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; crypto::EncryptedData encryptedMessage; try { encryptedMessage = contentCryptoModule->encrypt(deviceIDCpp, messageCpp); this->persistCryptoModules(true, false); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } auto encryptedDataJSI = jsi::Object(innerRt); auto message = std::string{ encryptedMessage.message.begin(), encryptedMessage.message.end()}; auto messageJSI = jsi::String::createFromUtf8(innerRt, message); encryptedDataJSI.setProperty(innerRt, "message", messageJSI); encryptedDataJSI.setProperty( innerRt, "messageType", static_cast(encryptedMessage.messageType)); promise->resolve(std::move(encryptedDataJSI)); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::encryptAndPersist( jsi::Runtime &rt, jsi::String message, jsi::String deviceID, jsi::String messageID) { auto messageCpp{message.utf8(rt)}; auto deviceIDCpp{deviceID.utf8(rt)}; auto messageIDCpp{messageID.utf8(rt)}; return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; crypto::EncryptedData encryptedMessage; try { encryptedMessage = contentCryptoModule->encrypt(deviceIDCpp, messageCpp); std::string storedSecretKey = getAccountDataKey(secureStoreAccountDataKey); crypto::Persist newContentPersist = this->contentCryptoModule->storeAsB64(storedSecretKey); std::promise persistencePromise; std::future persistenceFuture = persistencePromise.get_future(); GlobalDBSingleton::instance.scheduleOrRunCancellable( [=, &persistencePromise]() { try { folly::dynamic jsonObject = folly::dynamic::object; std::string messageStr( encryptedMessage.message.begin(), encryptedMessage.message.end()); jsonObject["message"] = messageStr; jsonObject["messageType"] = encryptedMessage.messageType; std::string ciphertext = folly::toJson(jsonObject); DatabaseManager::getQueryExecutor().beginTransaction(); DatabaseManager::getQueryExecutor() .setCiphertextForOutboundP2PMessage( messageIDCpp, deviceIDCpp, ciphertext); DatabaseManager::getQueryExecutor().storeOlmPersistData( DatabaseManager::getQueryExecutor() .getContentAccountID(), newContentPersist); DatabaseManager::getQueryExecutor().commitTransaction(); persistencePromise.set_value(); } catch (std::system_error &e) { DatabaseManager::getQueryExecutor().rollbackTransaction(); persistencePromise.set_exception( std::make_exception_ptr(e)); } }); persistenceFuture.get(); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } auto encryptedDataJSI = jsi::Object(innerRt); auto message = std::string{ encryptedMessage.message.begin(), encryptedMessage.message.end()}; auto messageJSI = jsi::String::createFromUtf8(innerRt, message); encryptedDataJSI.setProperty(innerRt, "message", messageJSI); encryptedDataJSI.setProperty( innerRt, "messageType", static_cast(encryptedMessage.messageType)); promise->resolve(std::move(encryptedDataJSI)); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::decrypt( jsi::Runtime &rt, jsi::Object encryptedDataJSI, jsi::String deviceID) { size_t messageType = std::lround(encryptedDataJSI.getProperty(rt, "messageType").asNumber()); std::string message = encryptedDataJSI.getProperty(rt, "message").asString(rt).utf8(rt); auto deviceIDCpp{deviceID.utf8(rt)}; return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; std::string decryptedMessage; try { crypto::EncryptedData encryptedData{ std::vector(message.begin(), message.end()), messageType}; decryptedMessage = this->contentCryptoModule->decrypt(deviceIDCpp, encryptedData); this->persistCryptoModules(true, false); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } promise->resolve( jsi::String::createFromUtf8(innerRt, decryptedMessage)); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::decryptSequentialAndPersist( jsi::Runtime &rt, jsi::Object encryptedDataJSI, jsi::String deviceID, jsi::String messageID) { size_t messageType = std::lround(encryptedDataJSI.getProperty(rt, "messageType").asNumber()); std::string message = encryptedDataJSI.getProperty(rt, "message").asString(rt).utf8(rt); auto deviceIDCpp{deviceID.utf8(rt)}; auto messageIDCpp{messageID.utf8(rt)}; return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; std::string decryptedMessage; try { crypto::EncryptedData encryptedData{ std::vector(message.begin(), message.end()), messageType}; decryptedMessage = this->contentCryptoModule->decryptSequential( deviceIDCpp, encryptedData); std::string storedSecretKey = getAccountDataKey(secureStoreAccountDataKey); crypto::Persist newContentPersist = this->contentCryptoModule->storeAsB64(storedSecretKey); std::promise persistencePromise; std::future persistenceFuture = persistencePromise.get_future(); GlobalDBSingleton::instance.scheduleOrRunCancellable( [=, &persistencePromise]() { try { InboundP2PMessage message{ messageIDCpp, deviceIDCpp, decryptedMessage, "decrypted"}; DatabaseManager::getQueryExecutor().beginTransaction(); DatabaseManager::getQueryExecutor().addInboundP2PMessage( message); DatabaseManager::getQueryExecutor().storeOlmPersistData( DatabaseManager::getQueryExecutor() .getContentAccountID(), newContentPersist); DatabaseManager::getQueryExecutor().commitTransaction(); persistencePromise.set_value(); } catch (std::system_error &e) { DatabaseManager::getQueryExecutor().rollbackTransaction(); persistencePromise.set_exception( std::make_exception_ptr(e)); } }); persistenceFuture.get(); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } promise->resolve( jsi::String::createFromUtf8(innerRt, decryptedMessage)); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::signMessage(jsi::Runtime &rt, jsi::String message) { std::string messageStr = message.utf8(rt); return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; std::string signature; try { signature = this->contentCryptoModule->signMessage(messageStr); } catch (const std::exception &e) { error = "signing message failed with: " + std::string(e.what()); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } auto jsiSignature{jsi::String::createFromUtf8(innerRt, signature)}; promise->resolve(std::move(jsiSignature)); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::verifySignature( jsi::Runtime &rt, jsi::String publicKey, jsi::String message, jsi::String signature) { std::string keyStr = publicKey.utf8(rt); std::string messageStr = message.utf8(rt); std::string signatureStr = signature.utf8(rt); return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; try { crypto::CryptoModule::verifySignature( keyStr, messageStr, signatureStr); } catch (const std::exception &e) { error = "verifying signature failed with: " + std::string(e.what()); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } promise->resolve(jsi::Value::undefined()); }); }; this->cryptoThread->scheduleTask(job); }); } CommCoreModule::CommCoreModule( std::shared_ptr jsInvoker) : facebook::react::CommCoreModuleSchemaCxxSpecJSI(jsInvoker), cryptoThread(std::make_unique("crypto")), draftStore(jsInvoker), threadStore(jsInvoker), messageStore(jsInvoker), reportStore(jsInvoker), userStore(jsInvoker), keyserverStore(jsInvoker), communityStore(jsInvoker), integrityStore(jsInvoker), syncedMetadataStore(jsInvoker), auxUserStore(jsInvoker), threadActivityStore(jsInvoker) { GlobalDBSingleton::instance.enableMultithreading(); } double CommCoreModule::getCodeVersion(jsi::Runtime &rt) { return this->codeVersion; } jsi::Value CommCoreModule::setNotifyToken(jsi::Runtime &rt, jsi::String token) { auto notifyToken{token.utf8(rt)}; return createPromiseAsJSIValue( rt, [this, notifyToken](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [this, notifyToken, promise]() { std::string error; try { DatabaseManager::getQueryExecutor().setNotifyToken(notifyToken); } catch (std::system_error &e) { error = e.what(); } this->jsInvoker_->invokeAsync([error, promise]() { if (error.size()) { promise->reject(error); } else { promise->resolve(jsi::Value::undefined()); } }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } jsi::Value CommCoreModule::clearNotifyToken(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [this](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [this, promise]() { std::string error; try { DatabaseManager::getQueryExecutor().clearNotifyToken(); } catch (std::system_error &e) { error = e.what(); } this->jsInvoker_->invokeAsync([error, promise]() { if (error.size()) { promise->reject(error); } else { promise->resolve(jsi::Value::undefined()); } }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); }; jsi::Value CommCoreModule::stampSQLiteDBUserID(jsi::Runtime &rt, jsi::String userID) { auto currentUserID{userID.utf8(rt)}; return createPromiseAsJSIValue( rt, [this, currentUserID](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [this, promise, currentUserID]() { std::string error; try { DatabaseManager::getQueryExecutor().stampSQLiteDBUserID( currentUserID); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([error, promise]() { if (error.size()) { promise->reject(error); } else { promise->resolve(jsi::Value::undefined()); } }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } jsi::Value CommCoreModule::getSQLiteStampedUserID(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [this](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [this, &innerRt, promise]() { std::string error; std::string result; try { result = DatabaseManager::getQueryExecutor().getSQLiteStampedUserID(); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([&innerRt, error, result, promise]() { if (error.size()) { promise->reject(error); } else { promise->resolve(jsi::String::createFromUtf8(innerRt, result)); } }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } jsi::Value CommCoreModule::clearSensitiveData(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [this](jsi::Runtime &innerRt, std::shared_ptr promise) { GlobalDBSingleton::instance.setTasksCancelled(true); taskType job = [this, promise]() { std::string error; try { this->innerClearCommServicesAuthMetadata(); DatabaseManager::clearSensitiveData(); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([error, promise]() { if (error.size()) { promise->reject(error); } else { promise->resolve(jsi::Value::undefined()); } }); GlobalDBSingleton::instance.scheduleOrRun( []() { GlobalDBSingleton::instance.setTasksCancelled(false); }); }; GlobalDBSingleton::instance.scheduleOrRun(job); }); } bool CommCoreModule::checkIfDatabaseNeedsDeletion(jsi::Runtime &rt) { return DatabaseManager::checkIfDatabaseNeedsDeletion(); } void CommCoreModule::reportDBOperationsFailure(jsi::Runtime &rt) { DatabaseManager::reportDBOperationsFailure(); } jsi::Value CommCoreModule::computeBackupKey( jsi::Runtime &rt, jsi::String password, jsi::String backupID) { std::string passwordStr = password.utf8(rt); std::string backupIDStr = backupID.utf8(rt); return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; std::array<::std::uint8_t, 32> backupKey; try { backupKey = compute_backup_key(passwordStr, backupIDStr); } catch (const std::exception &e) { error = std::string{"Failed to compute backup key: "} + e.what(); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } auto size = backupKey.size(); auto arrayBuffer = innerRt.global() .getPropertyAsFunction(innerRt, "ArrayBuffer") .callAsConstructor(innerRt, {static_cast(size)}) .asObject(innerRt) .getArrayBuffer(innerRt); auto bufferPtr = arrayBuffer.data(innerRt); memcpy(bufferPtr, backupKey.data(), size); promise->resolve(std::move(arrayBuffer)); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::generateRandomString(jsi::Runtime &rt, double size) { return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; std::string randomString; try { randomString = crypto::Tools::generateRandomString(static_cast(size)); } catch (const std::exception &e) { error = "Failed to generate random string for size " + std::to_string(size) + ": " + e.what(); } this->jsInvoker_->invokeAsync( [&innerRt, error, randomString, promise]() { if (error.size()) { promise->reject(error); } else { jsi::String jsiRandomString = jsi::String::createFromUtf8(innerRt, randomString); promise->resolve(std::move(jsiRandomString)); } }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::setCommServicesAuthMetadata( jsi::Runtime &rt, jsi::String userID, jsi::String deviceID, jsi::String accessToken) { auto userIDStr{userID.utf8(rt)}; auto deviceIDStr{deviceID.utf8(rt)}; auto accessTokenStr{accessToken.utf8(rt)}; return createPromiseAsJSIValue( rt, [this, userIDStr, deviceIDStr, accessTokenStr]( jsi::Runtime &innerRt, std::shared_ptr promise) { std::string error; try { this->innerSetCommServicesAuthMetadata( userIDStr, deviceIDStr, accessTokenStr); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([error, promise]() { if (error.size()) { promise->reject(error); } else { promise->resolve(jsi::Value::undefined()); } }); }); } void CommCoreModule::innerSetCommServicesAuthMetadata( std::string userID, std::string deviceID, std::string accessToken) { CommSecureStore::set(CommSecureStore::userID, userID); CommSecureStore::set(CommSecureStore::deviceID, deviceID); CommSecureStore::set(CommSecureStore::commServicesAccessToken, accessToken); CommServicesAuthMetadataEmitter::sendAuthMetadataToJS(accessToken, userID); } jsi::Value CommCoreModule::getCommServicesAuthMetadata(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [this](jsi::Runtime &innerRt, std::shared_ptr promise) { std::string error; std::string userID; std::string deviceID; std::string accessToken; try { folly::Optional userIDOpt = CommSecureStore::get(CommSecureStore::userID); if (userIDOpt.hasValue()) { userID = userIDOpt.value(); } folly::Optional deviceIDOpt = CommSecureStore::get(CommSecureStore::deviceID); if (deviceIDOpt.hasValue()) { deviceID = deviceIDOpt.value(); } folly::Optional accessTokenOpt = CommSecureStore::get(CommSecureStore::commServicesAccessToken); if (accessTokenOpt.hasValue()) { accessToken = accessTokenOpt.value(); } } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync( [&innerRt, error, userID, deviceID, accessToken, promise]() { if (error.size()) { promise->reject(error); } else { auto authMetadata = jsi::Object(innerRt); if (!userID.empty()) { authMetadata.setProperty( innerRt, "userID", jsi::String::createFromUtf8(innerRt, userID)); } if (!deviceID.empty()) { authMetadata.setProperty( innerRt, "deviceID", jsi::String::createFromUtf8(innerRt, deviceID)); } if (!accessToken.empty()) { authMetadata.setProperty( innerRt, "accessToken", jsi::String::createFromUtf8(innerRt, accessToken)); } promise->resolve(std::move(authMetadata)); } }); }); } jsi::Value CommCoreModule::clearCommServicesAuthMetadata(jsi::Runtime &rt) { return this->setCommServicesAuthMetadata( rt, jsi::String::createFromUtf8(rt, ""), jsi::String::createFromUtf8(rt, ""), jsi::String::createFromUtf8(rt, "")); } void CommCoreModule::innerClearCommServicesAuthMetadata() { return this->innerSetCommServicesAuthMetadata("", "", ""); } jsi::Value CommCoreModule::setCommServicesAccessToken( jsi::Runtime &rt, jsi::String accessToken) { auto accessTokenStr{accessToken.utf8(rt)}; return createPromiseAsJSIValue( rt, [this, accessTokenStr]( jsi::Runtime &innerRt, std::shared_ptr promise) { std::string error; try { CommSecureStore::set( CommSecureStore::commServicesAccessToken, accessTokenStr); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([error, promise]() { if (error.size()) { promise->reject(error); } else { promise->resolve(jsi::Value::undefined()); } }); }); } jsi::Value CommCoreModule::clearCommServicesAccessToken(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [this](jsi::Runtime &innerRt, std::shared_ptr promise) { std::string error; try { CommSecureStore::set(CommSecureStore::commServicesAccessToken, ""); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([error, promise]() { if (error.size()) { promise->reject(error); } else { promise->resolve(jsi::Value::undefined()); } }); }); } void CommCoreModule::startBackupHandler(jsi::Runtime &rt) { try { ::startBackupHandler(); } catch (const std::exception &e) { throw jsi::JSError(rt, e.what()); } } void CommCoreModule::stopBackupHandler(jsi::Runtime &rt) { try { ::stopBackupHandler(); } catch (const std::exception &e) { throw jsi::JSError(rt, e.what()); } } jsi::Value CommCoreModule::createNewBackupInternal( jsi::Runtime &rt, std::string backupSecret, std::string backupMessage) { return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { this->cryptoThread->scheduleTask([=, &innerRt]() { std::string error; std::string backupID; try { backupID = crypto::Tools::generateRandomString(32); } catch (const std::exception &e) { error = "Failed to generate backupID"; } std::string pickleKey; std::string pickledAccount; if (!error.size()) { try { pickleKey = crypto::Tools::generateRandomString(64); crypto::Persist persist = this->contentCryptoModule->storeAsB64(pickleKey); pickledAccount = std::string(persist.account.begin(), persist.account.end()); } catch (const std::exception &e) { error = "Failed to pickle crypto account"; } } if (!error.size()) { auto currentID = RustPromiseManager::instance.addPromise( {promise, this->jsInvoker_, innerRt}); ::createBackup( rust::string(backupID), rust::string(backupSecret), rust::string(pickleKey), rust::string(pickledAccount), rust::string(backupMessage), currentID); } else { this->jsInvoker_->invokeAsync( [=, &innerRt]() { promise->reject(error); }); } }); }); } jsi::Value CommCoreModule::createNewBackup(jsi::Runtime &rt, jsi::String backupSecret) { std::string backupSecretStr = backupSecret.utf8(rt); return createNewBackupInternal(rt, backupSecretStr, ""); } jsi::Value CommCoreModule::createNewSIWEBackup( jsi::Runtime &rt, jsi::String backupSecret, jsi::String siweBackupMsg) { std::string backupSecretStr = backupSecret.utf8(rt); std::string siweBackupMsgStr = siweBackupMsg.utf8(rt); return createNewBackupInternal(rt, backupSecretStr, siweBackupMsgStr); } jsi::Value CommCoreModule::restoreBackupInternal( jsi::Runtime &rt, std::string backupSecret, std::string backupID) { return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { auto currentID = RustPromiseManager::instance.addPromise( {promise, this->jsInvoker_, innerRt}); ::restoreBackup( rust::string(backupSecret), rust::string(backupID), currentID); }); } jsi::Value CommCoreModule::restoreBackup(jsi::Runtime &rt, jsi::String backupSecret) { std::string backupSecretStr = backupSecret.utf8(rt); return restoreBackupInternal(rt, backupSecretStr, ""); } jsi::Value CommCoreModule::restoreSIWEBackup( jsi::Runtime &rt, jsi::String backupSecret, jsi::String backupID) { std::string backupSecretStr = backupSecret.utf8(rt); std::string backupIDStr = backupID.utf8(rt); return restoreBackupInternal(rt, backupSecretStr, backupIDStr); } jsi::Value CommCoreModule::restoreBackupData( jsi::Runtime &rt, jsi::String backupID, jsi::String backupDataKey, jsi::String backupLogDataKey) { std::string backupIDStr = backupID.utf8(rt); std::string backupDataKeyStr = backupDataKey.utf8(rt); std::string backupLogDataKeyStr = backupLogDataKey.utf8(rt); return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { auto currentID = RustPromiseManager::instance.addPromise( {promise, this->jsInvoker_, innerRt}); ::restoreBackupData( rust::string(backupIDStr), rust::string(backupDataKeyStr), rust::string(backupLogDataKeyStr), currentID); }); } jsi::Value CommCoreModule::retrieveBackupKeys(jsi::Runtime &rt, jsi::String backupSecret) { std::string backupSecretStr = backupSecret.utf8(rt); return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { auto currentID = RustPromiseManager::instance.addPromise( {promise, this->jsInvoker_, innerRt}); ::retrieveBackupKeys(rust::string(backupSecretStr), currentID); }); } jsi::Value CommCoreModule::retrieveLatestSIWEBackupData(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { auto currentID = RustPromiseManager::instance.addPromise( {promise, this->jsInvoker_, innerRt}); ::retrieveLatestSIWEBackupData(currentID); }); } jsi::Value CommCoreModule::setSIWEBackupSecrets( jsi::Runtime &rt, jsi::Object siweBackupSecrets) { std::string message = siweBackupSecrets.getProperty(rt, "message").asString(rt).utf8(rt); std::string signature = siweBackupSecrets.getProperty(rt, "signature").asString(rt).utf8(rt); folly::dynamic backupSecretsJSON = folly::dynamic::object("message", message)("signature", signature); std::string backupSecrets = folly::toJson(backupSecretsJSON); return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [this, promise, backupSecrets]() { std::string error; try { DatabaseManager::getQueryExecutor().setMetadata( "siweBackupSecrets", backupSecrets); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([error, promise]() { if (error.size()) { promise->reject(error); } else { promise->resolve(jsi::Value::undefined()); } }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } jsi::Value CommCoreModule::getSIWEBackupSecrets(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [this](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [this, &innerRt, promise]() { std::string error; std::string backupSecrets; try { backupSecrets = DatabaseManager::getQueryExecutor().getMetadata( "siweBackupSecrets"); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync( [&innerRt, error, backupSecrets, promise]() { if (error.size()) { promise->reject(error); } else if (!backupSecrets.size()) { promise->resolve(jsi::Value::undefined()); } else { folly::dynamic backupSecretsJSON = folly::parseJson(backupSecrets); std::string message = backupSecretsJSON["message"].asString(); std::string signature = backupSecretsJSON["signature"].asString(); auto siweBackupSecrets = jsi::Object(innerRt); siweBackupSecrets.setProperty( innerRt, "message", jsi::String::createFromUtf8(innerRt, message)); siweBackupSecrets.setProperty( innerRt, "signature", jsi::String::createFromUtf8(innerRt, signature)); promise->resolve(std::move(siweBackupSecrets)); } }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } jsi::Value CommCoreModule::getAllInboundP2PMessage(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; std::vector messages; try { messages = DatabaseManager::getQueryExecutor().getAllInboundP2PMessage(); } catch (std::system_error &e) { error = e.what(); } auto messagesPtr = std::make_shared>( std::move(messages)); this->jsInvoker_->invokeAsync( [&innerRt, messagesPtr, error, promise]() { if (error.size()) { promise->reject(error); return; } jsi::Array jsiMessages = jsi::Array(innerRt, messagesPtr->size()); size_t writeIdx = 0; for (const InboundP2PMessage &msg : *messagesPtr) { jsi::Object jsiMsg = jsi::Object(innerRt); jsiMsg.setProperty(innerRt, "messageID", msg.message_id); jsiMsg.setProperty( innerRt, "senderDeviceID", msg.sender_device_id); jsiMsg.setProperty(innerRt, "plaintext", msg.plaintext); jsiMsg.setProperty(innerRt, "status", msg.status); jsiMessages.setValueAtIndex(innerRt, writeIdx++, jsiMsg); } promise->resolve(std::move(jsiMessages)); }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } jsi::Value CommCoreModule::removeInboundP2PMessages(jsi::Runtime &rt, jsi::Array ids) { std::vector msgIDsCPP{}; for (auto idx = 0; idx < ids.size(rt); idx++) { std::string msgID = ids.getValueAtIndex(rt, idx).asString(rt).utf8(rt); msgIDsCPP.push_back(msgID); } return createPromiseAsJSIValue( rt, [this, msgIDsCPP](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [this, promise, msgIDsCPP]() { std::string error; try { DatabaseManager::getQueryExecutor().removeInboundP2PMessages( msgIDsCPP); } catch (std::system_error &e) { error = e.what(); } this->jsInvoker_->invokeAsync([error, promise]() { if (error.size()) { promise->reject(error); } else { promise->resolve(jsi::Value::undefined()); } }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } jsi::Value CommCoreModule::getAllOutboundP2PMessage(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; std::vector messages; try { messages = DatabaseManager::getQueryExecutor().getAllOutboundP2PMessages(); } catch (std::system_error &e) { error = e.what(); } auto messagesPtr = std::make_shared>( std::move(messages)); this->jsInvoker_->invokeAsync( [&innerRt, messagesPtr, error, promise]() { if (error.size()) { promise->reject(error); return; } jsi::Array jsiMessages = jsi::Array(innerRt, messagesPtr->size()); size_t writeIdx = 0; for (const OutboundP2PMessage &msg : *messagesPtr) { jsi::Object jsiMsg = jsi::Object(innerRt); jsiMsg.setProperty(innerRt, "messageID", msg.message_id); jsiMsg.setProperty(innerRt, "deviceID", msg.device_id); jsiMsg.setProperty(innerRt, "userID", msg.user_id); jsiMsg.setProperty(innerRt, "timestamp", msg.timestamp); jsiMsg.setProperty(innerRt, "plaintext", msg.plaintext); jsiMsg.setProperty(innerRt, "ciphertext", msg.ciphertext); jsiMsg.setProperty(innerRt, "status", msg.status); jsiMessages.setValueAtIndex(innerRt, writeIdx++, jsiMsg); } promise->resolve(std::move(jsiMessages)); }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } jsi::Value CommCoreModule::markOutboundP2PMessageAsSent( jsi::Runtime &rt, jsi::String messageID, jsi::String deviceID) { auto messageIDCpp{messageID.utf8(rt)}; auto deviceIDCpp{deviceID.utf8(rt)}; return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=]() { std::string error; try { DatabaseManager::getQueryExecutor().markOutboundP2PMessageAsSent( messageIDCpp, deviceIDCpp); } catch (std::system_error &e) { error = e.what(); } this->jsInvoker_->invokeAsync([error, promise]() { if (error.size()) { promise->reject(error); } else { promise->resolve(jsi::Value::undefined()); } }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } jsi::Value CommCoreModule::removeOutboundP2PMessagesOlderThan( jsi::Runtime &rt, jsi::String messageID, jsi::String deviceID) { auto messageIDCpp{messageID.utf8(rt)}; auto deviceIDCpp{deviceID.utf8(rt)}; return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=]() { std::string error; try { DatabaseManager::getQueryExecutor() .removeOutboundP2PMessagesOlderThan(messageIDCpp, deviceIDCpp); } catch (std::system_error &e) { error = e.what(); } this->jsInvoker_->invokeAsync([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/redux/redux-utils.js b/native/redux/redux-utils.js index 325ad40d2..91d2acedb 100644 --- a/native/redux/redux-utils.js +++ b/native/redux/redux-utils.js @@ -1,114 +1,116 @@ // @flow import { useSelector as reactReduxUseSelector } from 'react-redux'; import { auxUserStoreOpsHandlers } from 'lib/ops/aux-user-store-ops.js'; import { communityStoreOpsHandlers } from 'lib/ops/community-store-ops.js'; import { integrityStoreOpsHandlers } from 'lib/ops/integrity-store-ops.js'; import { keyserverStoreOpsHandlers, getKeyserversToRemoveFromNotifsStore, } from 'lib/ops/keyserver-store-ops.js'; import { messageStoreOpsHandlers } from 'lib/ops/message-store-ops.js'; import { reportStoreOpsHandlers } from 'lib/ops/report-store-ops.js'; import { syncedMetadataStoreOpsHandlers } from 'lib/ops/synced-metadata-store-ops.js'; import { threadActivityStoreOpsHandlers } from 'lib/ops/thread-activity-store-ops.js'; import { threadStoreOpsHandlers } from 'lib/ops/thread-store-ops.js'; import { userStoreOpsHandlers } from 'lib/ops/user-store-ops.js'; import type { StoreOperations } from 'lib/types/store-ops-types.js'; import { values } from 'lib/utils/objects.js'; import type { AppState } from './state-types.js'; import { commCoreModule } from '../native-modules.js'; import { isTaskCancelledError } from '../utils/error-handling.js'; function useSelector( selector: (state: AppState) => SS, equalityFn?: (a: SS, b: SS) => boolean, ): SS { return reactReduxUseSelector(selector, equalityFn); } async function processDBStoreOperations( storeOperations: StoreOperations, ): Promise { const { draftStoreOperations, threadStoreOperations, messageStoreOperations, reportStoreOperations, userStoreOperations, keyserverStoreOperations, integrityStoreOperations, communityStoreOperations, syncedMetadataStoreOperations, auxUserStoreOperations, threadActivityStoreOperations, + outboundP2PMessages, } = storeOperations; const convertedThreadStoreOperations = threadStoreOpsHandlers.convertOpsToClientDBOps(threadStoreOperations); const convertedMessageStoreOperations = messageStoreOpsHandlers.convertOpsToClientDBOps(messageStoreOperations); const convertedReportStoreOperations = reportStoreOpsHandlers.convertOpsToClientDBOps(reportStoreOperations); const convertedUserStoreOperations = userStoreOpsHandlers.convertOpsToClientDBOps(userStoreOperations); const convertedKeyserverStoreOperations = keyserverStoreOpsHandlers.convertOpsToClientDBOps(keyserverStoreOperations); const convertedCommunityStoreOperations = communityStoreOpsHandlers.convertOpsToClientDBOps(communityStoreOperations); const convertedSyncedMetadataStoreOperations = syncedMetadataStoreOpsHandlers.convertOpsToClientDBOps( syncedMetadataStoreOperations, ); const keyserversToRemoveFromNotifsStore = getKeyserversToRemoveFromNotifsStore(keyserverStoreOperations ?? []); const convertedIntegrityStoreOperations = integrityStoreOpsHandlers.convertOpsToClientDBOps(integrityStoreOperations); const convertedAuxUserStoreOperations = auxUserStoreOpsHandlers.convertOpsToClientDBOps(auxUserStoreOperations); const convertedThreadActivityStoreOperations = threadActivityStoreOpsHandlers.convertOpsToClientDBOps( threadActivityStoreOperations, ); try { const promises = []; if (keyserversToRemoveFromNotifsStore.length > 0) { promises.push( commCoreModule.removeKeyserverDataFromNotifStorage( keyserversToRemoveFromNotifsStore, ), ); } const dbOps = { draftStoreOperations, threadStoreOperations: convertedThreadStoreOperations, messageStoreOperations: convertedMessageStoreOperations, reportStoreOperations: convertedReportStoreOperations, userStoreOperations: convertedUserStoreOperations, keyserverStoreOperations: convertedKeyserverStoreOperations, communityStoreOperations: convertedCommunityStoreOperations, integrityStoreOperations: convertedIntegrityStoreOperations, syncedMetadataStoreOperations: convertedSyncedMetadataStoreOperations, auxUserStoreOperations: convertedAuxUserStoreOperations, threadActivityStoreOperations: convertedThreadActivityStoreOperations, + outboundP2PMessages, }; if (values(dbOps).some(ops => ops && ops.length > 0)) { promises.push(commCoreModule.processDBStoreOperations(dbOps)); } await Promise.all(promises); } catch (e) { if (isTaskCancelledError(e)) { return; } // this code will make an entry in SecureStore and cause re-creating // database when user will open app again commCoreModule.reportDBOperationsFailure(); commCoreModule.terminate(); } } export { useSelector, processDBStoreOperations }; diff --git a/web/shared-worker/utils/store.js b/web/shared-worker/utils/store.js index 2d0b74a5a..f6634e75a 100644 --- a/web/shared-worker/utils/store.js +++ b/web/shared-worker/utils/store.js @@ -1,244 +1,246 @@ // @flow import { auxUserStoreOpsHandlers } from 'lib/ops/aux-user-store-ops.js'; import { communityStoreOpsHandlers } from 'lib/ops/community-store-ops.js'; import { integrityStoreOpsHandlers } from 'lib/ops/integrity-store-ops.js'; import { keyserverStoreOpsHandlers } from 'lib/ops/keyserver-store-ops.js'; import { messageStoreOpsHandlers } from 'lib/ops/message-store-ops.js'; import { reportStoreOpsHandlers } from 'lib/ops/report-store-ops.js'; import { syncedMetadataStoreOpsHandlers } from 'lib/ops/synced-metadata-store-ops.js'; import { threadActivityStoreOpsHandlers } from 'lib/ops/thread-activity-store-ops.js'; import { threadStoreOpsHandlers } from 'lib/ops/thread-store-ops.js'; import { userStoreOpsHandlers } from 'lib/ops/user-store-ops.js'; import { canUseDatabaseOnWeb } from 'lib/shared/web-database.js'; import type { ClientStore, StoreOperations, } from 'lib/types/store-ops-types.js'; import { entries } from 'lib/utils/objects.js'; import { defaultWebState } from '../../redux/default-state.js'; import { workerRequestMessageTypes } from '../../types/worker-types.js'; import { getCommSharedWorker } from '../shared-worker-provider.js'; async function getClientDBStore(): Promise { const sharedWorker = await getCommSharedWorker(); let result: ClientStore = { currentUserID: null, drafts: [], messages: null, threadStore: null, messageStoreThreads: null, reports: null, users: null, keyserverInfos: defaultWebState.keyserverStore.keyserverInfos, communityInfos: null, threadHashes: null, syncedMetadata: null, auxUserInfos: null, threadActivityStore: null, }; const data = await sharedWorker.schedule({ type: workerRequestMessageTypes.GET_CLIENT_STORE, }); if (data?.store?.drafts) { result = { ...result, drafts: data.store.drafts, }; } if (data?.store?.reports) { result = { ...result, reports: reportStoreOpsHandlers.translateClientDBData(data.store.reports), }; } if (data?.store?.threads && data.store.threads.length > 0) { result = { ...result, threadStore: { threadInfos: threadStoreOpsHandlers.translateClientDBData( data.store.threads, ), }, }; } if (data?.store?.keyservers?.length) { result = { ...result, keyserverInfos: keyserverStoreOpsHandlers.translateClientDBData( data.store.keyservers, ), }; } if (data?.store?.communities) { result = { ...result, communityInfos: communityStoreOpsHandlers.translateClientDBData( data.store.communities, ), }; } if (data?.store?.integrityThreadHashes) { result = { ...result, threadHashes: integrityStoreOpsHandlers.translateClientDBData( data.store.integrityThreadHashes, ), }; } if (data?.store?.syncedMetadata) { result = { ...result, syncedMetadata: syncedMetadataStoreOpsHandlers.translateClientDBData( data.store.syncedMetadata, ), }; } if (data?.store?.auxUserInfos) { result = { ...result, auxUserInfos: auxUserStoreOpsHandlers.translateClientDBData( data.store.auxUserInfos, ), }; } if (data?.store?.users && data.store.users.length > 0) { result = { ...result, users: userStoreOpsHandlers.translateClientDBData(data.store.users), }; } if (data?.store?.messages && data.store.messages.length > 0) { result = { ...result, messages: data.store.messages, }; } if ( data?.store?.messageStoreThreads && data.store.messageStoreThreads.length > 0 ) { result = { ...result, messageStoreThreads: data.store.messageStoreThreads, }; } if ( data?.store?.threadActivityEntries && data.store.threadActivityEntries.length > 0 ) { result = { ...result, threadActivityStore: threadActivityStoreOpsHandlers.translateClientDBData( data.store.threadActivityEntries, ), }; } return result; } async function processDBStoreOperations( storeOperations: StoreOperations, userID?: ?string, ): Promise { const { draftStoreOperations, threadStoreOperations, reportStoreOperations, keyserverStoreOperations, communityStoreOperations, integrityStoreOperations, syncedMetadataStoreOperations, auxUserStoreOperations, userStoreOperations, messageStoreOperations, threadActivityStoreOperations, + outboundP2PMessages, } = storeOperations; const canUseDatabase = canUseDatabaseOnWeb(userID); const convertedThreadStoreOperations = canUseDatabase ? threadStoreOpsHandlers.convertOpsToClientDBOps(threadStoreOperations) : []; const convertedReportStoreOperations = reportStoreOpsHandlers.convertOpsToClientDBOps(reportStoreOperations); const convertedKeyserverStoreOperations = keyserverStoreOpsHandlers.convertOpsToClientDBOps(keyserverStoreOperations); const convertedCommunityStoreOperations = communityStoreOpsHandlers.convertOpsToClientDBOps(communityStoreOperations); const convertedIntegrityStoreOperations = integrityStoreOpsHandlers.convertOpsToClientDBOps(integrityStoreOperations); const convertedSyncedMetadataStoreOperations = syncedMetadataStoreOpsHandlers.convertOpsToClientDBOps( syncedMetadataStoreOperations, ); const convertedAuxUserStoreOperations = auxUserStoreOpsHandlers.convertOpsToClientDBOps(auxUserStoreOperations); const convertedUserStoreOperations = userStoreOpsHandlers.convertOpsToClientDBOps(userStoreOperations); const convertedMessageStoreOperations = messageStoreOpsHandlers.convertOpsToClientDBOps(messageStoreOperations); const convertedThreadActivityStoreOperations = threadActivityStoreOpsHandlers.convertOpsToClientDBOps( threadActivityStoreOperations, ); if ( convertedThreadStoreOperations.length === 0 && convertedReportStoreOperations.length === 0 && (!draftStoreOperations || draftStoreOperations.length === 0) && convertedKeyserverStoreOperations.length === 0 && convertedCommunityStoreOperations.length === 0 && convertedIntegrityStoreOperations.length === 0 && convertedSyncedMetadataStoreOperations.length === 0 && convertedAuxUserStoreOperations.length === 0 && convertedUserStoreOperations.length === 0 && convertedMessageStoreOperations.length === 0 && convertedThreadActivityStoreOperations.length === 0 ) { return; } const sharedWorker = await getCommSharedWorker(); const isSupported = await sharedWorker.isSupported(); if (!isSupported) { return; } try { await sharedWorker.schedule({ type: workerRequestMessageTypes.PROCESS_STORE_OPERATIONS, storeOperations: { draftStoreOperations, reportStoreOperations: convertedReportStoreOperations, threadStoreOperations: convertedThreadStoreOperations, keyserverStoreOperations: convertedKeyserverStoreOperations, communityStoreOperations: convertedCommunityStoreOperations, integrityStoreOperations: convertedIntegrityStoreOperations, syncedMetadataStoreOperations: convertedSyncedMetadataStoreOperations, auxUserStoreOperations: convertedAuxUserStoreOperations, userStoreOperations: convertedUserStoreOperations, messageStoreOperations: convertedMessageStoreOperations, threadActivityStoreOperations: convertedThreadActivityStoreOperations, + outboundP2PMessages, }, }); } catch (e) { console.log(e); if (canUseDatabase) { window.alert(e.message); if ( entries(storeOperations).some( ([key, ops]) => key !== 'draftStoreOperations' && key !== 'reportStoreOperations' && ops.length > 0, ) ) { await sharedWorker.init({ clearDatabase: true, markAsCorrupted: true }); location.reload(); } } } } export { getClientDBStore, processDBStoreOperations }; diff --git a/web/shared-worker/worker/process-operations.js b/web/shared-worker/worker/process-operations.js index a83c844fa..ebfc61005 100644 --- a/web/shared-worker/worker/process-operations.js +++ b/web/shared-worker/worker/process-operations.js @@ -1,557 +1,561 @@ // @flow import type { ClientDBAuxUserStoreOperation } from 'lib/ops/aux-user-store-ops.js'; import type { ClientDBCommunityStoreOperation } from 'lib/ops/community-store-ops.js'; import type { ClientDBIntegrityStoreOperation } from 'lib/ops/integrity-store-ops.js'; import type { ClientDBKeyserverStoreOperation } from 'lib/ops/keyserver-store-ops.js'; import type { ClientDBMessageStoreOperation } from 'lib/ops/message-store-ops.js'; import type { ClientDBReportStoreOperation } from 'lib/ops/report-store-ops.js'; import type { ClientDBSyncedMetadataStoreOperation } from 'lib/ops/synced-metadata-store-ops.js'; import type { ClientDBThreadActivityStoreOperation } from 'lib/ops/thread-activity-store-ops.js'; import type { ClientDBThreadStoreOperation } from 'lib/ops/thread-store-ops.js'; import type { ClientDBUserStoreOperation } from 'lib/ops/user-store-ops.js'; import type { ClientDBDraftStoreOperation, DraftStoreOperation, } from 'lib/types/draft-types.js'; import type { ClientDBStore, ClientDBStoreOperations, } from 'lib/types/store-ops-types.js'; import { getMessageForException } from 'lib/utils/errors.js'; import { clientDBThreadInfoToWebThread, webThreadToClientDBThreadInfo, webMessageToClientDBMessageInfo, clientDBMessageInfoToWebMessage, } from '../types/entities.js'; import type { EmscriptenModule } from '../types/module.js'; import type { SQLiteQueryExecutor } from '../types/sqlite-query-executor.js'; function getProcessingStoreOpsExceptionMessage( e: mixed, module: EmscriptenModule, ): string { if (typeof e === 'number') { return module.getExceptionMessage(e); } return getMessageForException(e) ?? 'unknown error'; } function processDraftStoreOperations( sqliteQueryExecutor: SQLiteQueryExecutor, operations: $ReadOnlyArray, module: EmscriptenModule, ) { for (const operation: DraftStoreOperation of operations) { try { if (operation.type === 'remove_all') { sqliteQueryExecutor.removeAllDrafts(); } else if (operation.type === 'remove') { const { ids } = operation.payload; sqliteQueryExecutor.removeDrafts(ids); } else if (operation.type === 'update') { const { key, text } = operation.payload; sqliteQueryExecutor.updateDraft(key, text); } else if (operation.type === 'move') { const { oldKey, newKey } = operation.payload; sqliteQueryExecutor.moveDraft(oldKey, newKey); } else { throw new Error('Unsupported draft operation'); } } catch (e) { throw new Error( `Error while processing ${ operation.type } draft operation: ${getProcessingStoreOpsExceptionMessage(e, module)}`, ); } } } function processReportStoreOperations( sqliteQueryExecutor: SQLiteQueryExecutor, operations: $ReadOnlyArray, module: EmscriptenModule, ) { for (const operation: ClientDBReportStoreOperation of operations) { try { if (operation.type === 'remove_all_reports') { sqliteQueryExecutor.removeAllReports(); } else if (operation.type === 'remove_reports') { const { ids } = operation.payload; sqliteQueryExecutor.removeReports(ids); } else if (operation.type === 'replace_report') { const { id, report } = operation.payload; sqliteQueryExecutor.replaceReport({ id, report }); } else { throw new Error('Unsupported report operation'); } } catch (e) { throw new Error( `Error while processing ${ operation.type } report operation: ${getProcessingStoreOpsExceptionMessage( e, module, )}`, ); } } } function processThreadStoreOperations( sqliteQueryExecutor: SQLiteQueryExecutor, operations: $ReadOnlyArray, module: EmscriptenModule, ) { for (const operation: ClientDBThreadStoreOperation of operations) { try { if (operation.type === 'remove_all') { sqliteQueryExecutor.removeAllThreads(); } else if (operation.type === 'remove') { const { ids } = operation.payload; sqliteQueryExecutor.removeThreads(ids); } else if (operation.type === 'replace') { sqliteQueryExecutor.replaceThreadWeb( clientDBThreadInfoToWebThread(operation.payload), ); } else { throw new Error('Unsupported thread operation'); } } catch (e) { throw new Error( `Error while processing ${ operation.type } thread operation: ${getProcessingStoreOpsExceptionMessage( e, module, )}`, ); } } } function processKeyserverStoreOperations( sqliteQueryExecutor: SQLiteQueryExecutor, operations: $ReadOnlyArray, module: EmscriptenModule, ) { for (const operation: ClientDBKeyserverStoreOperation of operations) { try { if (operation.type === 'remove_all_keyservers') { sqliteQueryExecutor.removeAllKeyservers(); } else if (operation.type === 'remove_keyservers') { const { ids } = operation.payload; sqliteQueryExecutor.removeKeyservers(ids); } else if (operation.type === 'replace_keyserver') { const { id, keyserverInfo, syncedKeyserverInfo } = operation.payload; sqliteQueryExecutor.replaceKeyserver({ id, keyserverInfo, syncedKeyserverInfo, }); } else { throw new Error('Unsupported keyserver operation'); } } catch (e) { throw new Error( `Error while processing ${ operation.type } keyserver operation: ${getProcessingStoreOpsExceptionMessage( e, module, )}`, ); } } } function processCommunityStoreOperations( sqliteQueryExecutor: SQLiteQueryExecutor, operations: $ReadOnlyArray, module: EmscriptenModule, ) { for (const operation: ClientDBCommunityStoreOperation of operations) { try { if (operation.type === 'remove_all_communities') { sqliteQueryExecutor.removeAllCommunities(); } else if (operation.type === 'remove_communities') { const { ids } = operation.payload; sqliteQueryExecutor.removeCommunities(ids); } else if (operation.type === 'replace_community') { const { id, communityInfo } = operation.payload; sqliteQueryExecutor.replaceCommunity({ id, communityInfo }); } else { throw new Error('Unsupported community operation'); } } catch (e) { throw new Error( `Error while processing ${ operation.type } community operation: ${getProcessingStoreOpsExceptionMessage( e, module, )}`, ); } } } function processIntegrityStoreOperations( sqliteQueryExecutor: SQLiteQueryExecutor, operations: $ReadOnlyArray, module: EmscriptenModule, ) { for (const operation: ClientDBIntegrityStoreOperation of operations) { try { if (operation.type === 'remove_all_integrity_thread_hashes') { sqliteQueryExecutor.removeAllIntegrityThreadHashes(); } else if (operation.type === 'remove_integrity_thread_hashes') { const { ids } = operation.payload; sqliteQueryExecutor.removeIntegrityThreadHashes(ids); } else if (operation.type === 'replace_integrity_thread_hashes') { const { threadHashes } = operation.payload; sqliteQueryExecutor.replaceIntegrityThreadHashes(threadHashes); } else { throw new Error('Unsupported integrity operation'); } } catch (e) { throw new Error( `Error while processing ${ operation.type } integrity operation: ${getProcessingStoreOpsExceptionMessage( e, module, )}`, ); } } } function processSyncedMetadataStoreOperations( sqliteQueryExecutor: SQLiteQueryExecutor, operations: $ReadOnlyArray, module: EmscriptenModule, ) { for (const operation: ClientDBSyncedMetadataStoreOperation of operations) { try { if (operation.type === 'remove_all_synced_metadata') { sqliteQueryExecutor.removeAllSyncedMetadata(); } else if (operation.type === 'remove_synced_metadata') { const { names } = operation.payload; sqliteQueryExecutor.removeSyncedMetadata(names); } else if (operation.type === 'replace_synced_metadata_entry') { const { name, data } = operation.payload; sqliteQueryExecutor.replaceSyncedMetadataEntry({ name, data }); } else { throw new Error('Unsupported synced metadata operation'); } } catch (e) { throw new Error( `Error while processing ${ operation.type } synced metadata operation: ${getProcessingStoreOpsExceptionMessage( e, module, )}`, ); } } } function processMessageStoreOperations( sqliteQueryExecutor: SQLiteQueryExecutor, operations: $ReadOnlyArray, module: EmscriptenModule, ) { for (const operation of operations) { try { if (operation.type === 'rekey') { const { from, to } = operation.payload; sqliteQueryExecutor.rekeyMessage(from, to); } else if (operation.type === 'remove') { const { ids } = operation.payload; sqliteQueryExecutor.removeMessages(ids); } else if (operation.type === 'replace') { const { message, medias } = clientDBMessageInfoToWebMessage( operation.payload, ); sqliteQueryExecutor.replaceMessageWeb(message); for (const media of medias) { sqliteQueryExecutor.replaceMedia(media); } } else if (operation.type === 'remove_all') { sqliteQueryExecutor.removeAllMessages(); sqliteQueryExecutor.removeAllMedia(); } else if (operation.type === 'remove_threads') { const { ids } = operation.payload; sqliteQueryExecutor.removeMessageStoreThreads(ids); } else if (operation.type === 'replace_threads') { const { threads } = operation.payload; sqliteQueryExecutor.replaceMessageStoreThreads( threads.map(({ id, start_reached }) => ({ id, startReached: Number(start_reached), })), ); } else if (operation.type === 'remove_all_threads') { sqliteQueryExecutor.removeAllMessageStoreThreads(); } else if (operation.type === 'remove_messages_for_threads') { const { threadIDs } = operation.payload; sqliteQueryExecutor.removeMessagesForThreads(threadIDs); } else { throw new Error('Unsupported message operation'); } } catch (e) { throw new Error( `Error while processing ${ operation.type } message operation: ${getProcessingStoreOpsExceptionMessage( e, module, )}`, ); } } } function processUserStoreOperations( sqliteQueryExecutor: SQLiteQueryExecutor, operations: $ReadOnlyArray, module: EmscriptenModule, ) { for (const operation of operations) { try { if (operation.type === 'remove_users') { const { ids } = operation.payload; sqliteQueryExecutor.removeUsers(ids); } else if (operation.type === 'replace_user') { const user = operation.payload; sqliteQueryExecutor.replaceUser(user); } else if (operation.type === 'remove_all_users') { sqliteQueryExecutor.removeAllUsers(); } else { throw new Error('Unsupported user operation'); } } catch (e) { throw new Error( `Error while processing ${ operation.type } user operation: ${getProcessingStoreOpsExceptionMessage(e, module)}`, ); } } } function processDBStoreOperations( sqliteQueryExecutor: SQLiteQueryExecutor, storeOperations: ClientDBStoreOperations, module: EmscriptenModule, ) { const { draftStoreOperations, reportStoreOperations, threadStoreOperations, keyserverStoreOperations, communityStoreOperations, integrityStoreOperations, syncedMetadataStoreOperations, auxUserStoreOperations, userStoreOperations, messageStoreOperations, threadActivityStoreOperations, + outboundP2PMessages, } = storeOperations; try { sqliteQueryExecutor.beginTransaction(); if (draftStoreOperations && draftStoreOperations.length > 0) { processDraftStoreOperations( sqliteQueryExecutor, draftStoreOperations, module, ); } if (reportStoreOperations && reportStoreOperations.length > 0) { processReportStoreOperations( sqliteQueryExecutor, reportStoreOperations, module, ); } if (threadStoreOperations && threadStoreOperations.length > 0) { processThreadStoreOperations( sqliteQueryExecutor, threadStoreOperations, module, ); } if (keyserverStoreOperations && keyserverStoreOperations.length > 0) { processKeyserverStoreOperations( sqliteQueryExecutor, keyserverStoreOperations, module, ); } if (communityStoreOperations && communityStoreOperations.length > 0) { processCommunityStoreOperations( sqliteQueryExecutor, communityStoreOperations, module, ); } if (integrityStoreOperations && integrityStoreOperations.length > 0) { processIntegrityStoreOperations( sqliteQueryExecutor, integrityStoreOperations, module, ); } if ( syncedMetadataStoreOperations && syncedMetadataStoreOperations.length > 0 ) { processSyncedMetadataStoreOperations( sqliteQueryExecutor, syncedMetadataStoreOperations, module, ); } if (auxUserStoreOperations && auxUserStoreOperations.length > 0) { processAuxUserStoreOperations( sqliteQueryExecutor, auxUserStoreOperations, module, ); } if (userStoreOperations && userStoreOperations.length > 0) { processUserStoreOperations( sqliteQueryExecutor, userStoreOperations, module, ); } if (messageStoreOperations && messageStoreOperations.length > 0) { processMessageStoreOperations( sqliteQueryExecutor, messageStoreOperations, module, ); } if ( threadActivityStoreOperations && threadActivityStoreOperations.length > 0 ) { processThreadActivityStoreOperations( sqliteQueryExecutor, threadActivityStoreOperations, module, ); } + if (outboundP2PMessages && outboundP2PMessages.length > 0) { + sqliteQueryExecutor.addOutboundP2PMessages(outboundP2PMessages); + } sqliteQueryExecutor.commitTransaction(); } catch (e) { sqliteQueryExecutor.rollbackTransaction(); console.log('Error while processing store ops: ', e); throw e; } } function processAuxUserStoreOperations( sqliteQueryExecutor: SQLiteQueryExecutor, operations: $ReadOnlyArray, module: EmscriptenModule, ) { for (const operation: ClientDBAuxUserStoreOperation of operations) { try { if (operation.type === 'remove_all_aux_user_infos') { sqliteQueryExecutor.removeAllAuxUserInfos(); } else if (operation.type === 'remove_aux_user_infos') { const { ids } = operation.payload; sqliteQueryExecutor.removeAuxUserInfos(ids); } else if (operation.type === 'replace_aux_user_info') { const { id, auxUserInfo } = operation.payload; sqliteQueryExecutor.replaceAuxUserInfo({ id, auxUserInfo }); } else { throw new Error('Unsupported aux user operation'); } } catch (e) { throw new Error( `Error while processing ${ operation.type } aux user operation: ${getProcessingStoreOpsExceptionMessage( e, module, )}`, ); } } } function processThreadActivityStoreOperations( sqliteQueryExecutor: SQLiteQueryExecutor, operations: $ReadOnlyArray, module: EmscriptenModule, ) { for (const operation: ClientDBThreadActivityStoreOperation of operations) { try { if (operation.type === 'remove_all_thread_activity_entries') { sqliteQueryExecutor.removeAllThreadActivityEntries(); } else if (operation.type === 'remove_thread_activity_entries') { const { ids } = operation.payload; sqliteQueryExecutor.removeThreadActivityEntries(ids); } else if (operation.type === 'replace_thread_activity_entry') { const { id, threadActivityStoreEntry } = operation.payload; sqliteQueryExecutor.replaceThreadActivityEntry({ id, threadActivityStoreEntry, }); } else { throw new Error('Unsupported thread activity operation'); } } catch (e) { throw new Error( `Error while processing ${ operation.type } thread activity operation: ${getProcessingStoreOpsExceptionMessage( e, module, )}`, ); } } } function getClientStoreFromQueryExecutor( sqliteQueryExecutor: SQLiteQueryExecutor, ): ClientDBStore { return { drafts: sqliteQueryExecutor.getAllDrafts(), messages: sqliteQueryExecutor .getAllMessagesWeb() .map(webMessageToClientDBMessageInfo), threads: sqliteQueryExecutor .getAllThreadsWeb() .map(webThreadToClientDBThreadInfo), messageStoreThreads: sqliteQueryExecutor .getAllMessageStoreThreads() .map(({ id, startReached }) => ({ id, start_reached: startReached.toString(), })), reports: sqliteQueryExecutor.getAllReports(), users: sqliteQueryExecutor.getAllUsers(), keyservers: sqliteQueryExecutor.getAllKeyservers(), communities: sqliteQueryExecutor.getAllCommunities(), integrityThreadHashes: sqliteQueryExecutor.getAllIntegrityThreadHashes(), syncedMetadata: sqliteQueryExecutor.getAllSyncedMetadata(), auxUserInfos: sqliteQueryExecutor.getAllAuxUserInfos(), threadActivityEntries: sqliteQueryExecutor.getAllThreadActivityEntries(), }; } export { processDBStoreOperations, getProcessingStoreOpsExceptionMessage, getClientStoreFromQueryExecutor, };