diff --git a/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp b/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp index 4b8bb9d14..929af51b8 100644 --- a/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp +++ b/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp @@ -1,1169 +1,1171 @@ #include "CommCoreModule.h" #include "../CryptoTools/DeviceID.h" #include "DatabaseManager.h" #include "DraftStoreOperations.h" #include "InternalModules/GlobalDBSingleton.h" #include "MessageStoreOperations.h" #include "ThreadStoreOperations.h" #include #include namespace comm { using namespace facebook::react; template T CommCoreModule::runSyncOrThrowJSError( jsi::Runtime &rt, std::function task) { std::promise promise; GlobalDBSingleton::instance.scheduleOrRunCancellable([&promise, &task]() { try { if constexpr (std::is_void::value) { task(); promise.set_value(); } else { promise.set_value(task()); } } catch (const std::exception &e) { promise.set_exception(std::make_exception_ptr(e)); } }); // We cannot instantiate JSError on database thread, so // on the main thread we re-throw C++ error, catch it and // transform to informative JSError on the main thread try { return promise.get_future().get(); } catch (const std::exception &e) { throw jsi::JSError(rt, e.what()); } } jsi::Value CommCoreModule::getDraft(jsi::Runtime &rt, const 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, const jsi::Object &draft) { - std::string keyStr = draft.getProperty(rt, "key").asString(rt).utf8(rt); - std::string textStr = draft.getProperty(rt, "text").asString(rt).utf8(rt); +jsi::Value CommCoreModule::updateDraft( + jsi::Runtime &rt, + const jsi::String &key, + const 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, const jsi::String &oldKey, const 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::getAllDrafts(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; std::vector draftsVector; size_t numDrafts; try { draftsVector = DatabaseManager::getQueryExecutor().getAllDrafts(); numDrafts = count_if( draftsVector.begin(), draftsVector.end(), [](Draft draft) { return !draft.text.empty(); }); } catch (std::system_error &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } jsi::Array jsiDrafts = jsi::Array(innerRt, numDrafts); size_t writeIndex = 0; for (Draft draft : draftsVector) { if (draft.text.empty()) { continue; } auto jsiDraft = jsi::Object(innerRt); jsiDraft.setProperty(innerRt, "key", draft.key); jsiDraft.setProperty(innerRt, "text", draft.text); jsiDrafts.setValueAtIndex(innerRt, writeIndex++, jsiDraft); } promise->resolve(std::move(jsiDrafts)); }); }; 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 = this->runSyncOrThrowJSError< std::vector>>>(rt, []() { return DatabaseManager::getQueryExecutor().getAllMessages(); }); size_t numMessages{messagesVector.size()}; jsi::Array jsiMessages = jsi::Array(rt, numMessages); size_t writeIndex = 0; for (const auto &[message, media] : messagesVector) { auto jsiMessage = jsi::Object(rt); jsiMessage.setProperty(rt, "id", message.id); if (message.local_id) { auto local_id = message.local_id.get(); jsiMessage.setProperty(rt, "local_id", *local_id); } jsiMessage.setProperty(rt, "thread", message.thread); jsiMessage.setProperty(rt, "user", message.user); jsiMessage.setProperty(rt, "type", std::to_string(message.type)); if (message.future_type) { auto future_type = message.future_type.get(); jsiMessage.setProperty(rt, "future_type", std::to_string(*future_type)); } if (message.content) { auto content = message.content.get(); jsiMessage.setProperty(rt, "content", *content); } jsiMessage.setProperty(rt, "time", std::to_string(message.time)); size_t media_idx = 0; jsi::Array jsiMediaArray = jsi::Array(rt, media.size()); for (const auto &media_info : media) { auto jsiMedia = jsi::Object(rt); jsiMedia.setProperty(rt, "id", media_info.id); jsiMedia.setProperty(rt, "uri", media_info.uri); jsiMedia.setProperty(rt, "type", media_info.type); jsiMedia.setProperty(rt, "extras", media_info.extras); jsiMediaArray.setValueAtIndex(rt, media_idx++, jsiMedia); } jsiMessage.setProperty(rt, "media_infos", jsiMediaArray); jsiMessages.setValueAtIndex(rt, writeIndex++, jsiMessage); } return jsiMessages; } jsi::Value CommCoreModule::getAllMessages(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; std::vector>> messagesVector; size_t numMessages; try { messagesVector = DatabaseManager::getQueryExecutor().getAllMessages(); numMessages = messagesVector.size(); } catch (std::system_error &e) { error = e.what(); } auto messagesVectorPtr = std::make_shared< std::vector>>>( std::move(messagesVector)); this->jsInvoker_->invokeAsync( [messagesVectorPtr, &innerRt, promise, error, numMessages]() { if (error.size()) { promise->reject(error); return; } jsi::Array jsiMessages = jsi::Array(innerRt, numMessages); size_t writeIndex = 0; for (const auto &[message, media] : *messagesVectorPtr) { auto jsiMessage = jsi::Object(innerRt); jsiMessage.setProperty(innerRt, "id", message.id); if (message.local_id) { auto local_id = message.local_id.get(); jsiMessage.setProperty(innerRt, "local_id", *local_id); } jsiMessage.setProperty(innerRt, "thread", message.thread); jsiMessage.setProperty(innerRt, "user", message.user); jsiMessage.setProperty( innerRt, "type", std::to_string(message.type)); if (message.future_type) { auto future_type = message.future_type.get(); jsiMessage.setProperty( innerRt, "future_type", std::to_string(*future_type)); } if (message.content) { auto content = message.content.get(); jsiMessage.setProperty(innerRt, "content", *content); } jsiMessage.setProperty( innerRt, "time", std::to_string(message.time)); size_t media_idx = 0; jsi::Array jsiMediaArray = jsi::Array(innerRt, media.size()); for (const auto &media_info : media) { auto jsiMedia = jsi::Object(innerRt); jsiMedia.setProperty(innerRt, "id", media_info.id); jsiMedia.setProperty(innerRt, "uri", media_info.uri); jsiMedia.setProperty(innerRt, "type", media_info.type); jsiMedia.setProperty(innerRt, "extras", media_info.extras); jsiMediaArray.setValueAtIndex( innerRt, media_idx++, jsiMedia); } jsiMessage.setProperty(innerRt, "media_infos", jsiMediaArray); jsiMessages.setValueAtIndex( innerRt, writeIndex++, jsiMessage); } promise->resolve(std::move(jsiMessages)); }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } const std::string UPDATE_DRAFT_OPERATION = "update"; const std::string MOVE_DRAFT_OPERATION = "move"; const std::string REMOVE_ALL_DRAFTS_OPERATION = "remove_all"; std::vector> createDraftStoreOperations(jsi::Runtime &rt, const jsi::Array &operations) { std::vector> draftStoreOps; for (auto idx = 0; idx < operations.size(rt); idx++) { auto op = operations.getValueAtIndex(rt, idx).asObject(rt); auto op_type = op.getProperty(rt, "type").asString(rt).utf8(rt); if (op_type == REMOVE_ALL_DRAFTS_OPERATION) { draftStoreOps.push_back(std::make_unique()); continue; } auto payload_obj = op.getProperty(rt, "payload").asObject(rt); if (op_type == UPDATE_DRAFT_OPERATION) { draftStoreOps.push_back( std::make_unique(rt, payload_obj)); } else if (op_type == MOVE_DRAFT_OPERATION) { draftStoreOps.push_back( std::make_unique(rt, payload_obj)); } else { throw std::runtime_error("unsupported operation: " + op_type); } } return draftStoreOps; } jsi::Value CommCoreModule::processDraftStoreOperations( jsi::Runtime &rt, const jsi::Array &operations) { std::string createOperationsError; std::shared_ptr>> draftStoreOpsPtr; try { auto draftStoreOps = createDraftStoreOperations(rt, operations); draftStoreOpsPtr = std::make_shared>>( std::move(draftStoreOps)); } catch (std::runtime_error &e) { createOperationsError = e.what(); } return 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 : *draftStoreOpsPtr) { operation->execute(); } DatabaseManager::getQueryExecutor().commitTransaction(); } catch (std::system_error &e) { error = e.what(); DatabaseManager::getQueryExecutor().rollbackTransaction(); } } this->jsInvoker_->invokeAsync([=]() { if (error.size()) { promise->reject(error); } else { promise->resolve(jsi::Value::undefined()); } }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } const std::string REKEY_OPERATION = "rekey"; const std::string REMOVE_OPERATION = "remove"; const std::string REPLACE_OPERATION = "replace"; const std::string REMOVE_MSGS_FOR_THREADS_OPERATION = "remove_messages_for_threads"; const std::string REMOVE_ALL_OPERATION = "remove_all"; std::vector> createMessageStoreOperations(jsi::Runtime &rt, const jsi::Array &operations) { std::vector> messageStoreOps; for (auto idx = 0; idx < operations.size(rt); idx++) { auto op = operations.getValueAtIndex(rt, idx).asObject(rt); auto op_type = op.getProperty(rt, "type").asString(rt).utf8(rt); if (op_type == REMOVE_ALL_OPERATION) { messageStoreOps.push_back(std::make_unique()); continue; } auto payload_obj = op.getProperty(rt, "payload").asObject(rt); if (op_type == REMOVE_OPERATION) { messageStoreOps.push_back( std::make_unique(rt, payload_obj)); } else if (op_type == REMOVE_MSGS_FOR_THREADS_OPERATION) { messageStoreOps.push_back( std::make_unique(rt, payload_obj)); } else if (op_type == REPLACE_OPERATION) { messageStoreOps.push_back( std::make_unique(rt, payload_obj)); } else if (op_type == REKEY_OPERATION) { messageStoreOps.push_back( std::make_unique(rt, payload_obj)); } else { throw std::runtime_error("unsupported operation: " + op_type); } } return messageStoreOps; } jsi::Value CommCoreModule::processMessageStoreOperations( jsi::Runtime &rt, const jsi::Array &operations) { std::string createOperationsError; std::shared_ptr>> messageStoreOpsPtr; try { auto messageStoreOps = createMessageStoreOperations(rt, operations); messageStoreOpsPtr = std::make_shared< std::vector>>( std::move(messageStoreOps)); } catch (std::runtime_error &e) { createOperationsError = e.what(); } return 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 : *messageStoreOpsPtr) { operation->execute(); } DatabaseManager::getQueryExecutor().commitTransaction(); } catch (std::system_error &e) { error = e.what(); DatabaseManager::getQueryExecutor().rollbackTransaction(); } } this->jsInvoker_->invokeAsync([=]() { if (error.size()) { promise->reject(error); } else { promise->resolve(jsi::Value::undefined()); } }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } void CommCoreModule::processMessageStoreOperationsSync( jsi::Runtime &rt, const jsi::Array &operations) { std::vector> messageStoreOps; try { messageStoreOps = createMessageStoreOperations(rt, operations); } catch (const std::exception &e) { throw jsi::JSError(rt, e.what()); } this->runSyncOrThrowJSError(rt, [&messageStoreOps]() { try { DatabaseManager::getQueryExecutor().beginTransaction(); for (const auto &operation : messageStoreOps) { operation->execute(); } DatabaseManager::getQueryExecutor().commitTransaction(); } catch (const std::exception &e) { DatabaseManager::getQueryExecutor().rollbackTransaction(); throw e; } }); } jsi::Value CommCoreModule::getAllThreads(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; std::vector threadsVector; size_t numThreads; try { threadsVector = DatabaseManager::getQueryExecutor().getAllThreads(); numThreads = threadsVector.size(); } catch (std::system_error &e) { error = e.what(); } auto threadsVectorPtr = std::make_shared>(std::move(threadsVector)); this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } jsi::Array jsiThreads = jsi::Array(innerRt, numThreads); size_t writeIdx = 0; for (const Thread &thread : *threadsVectorPtr) { jsi::Object jsiThread = jsi::Object(innerRt); jsiThread.setProperty(innerRt, "id", thread.id); jsiThread.setProperty(innerRt, "type", thread.type); jsiThread.setProperty( innerRt, "name", thread.name ? jsi::String::createFromUtf8(innerRt, *thread.name) : jsi::Value::null()); jsiThread.setProperty( innerRt, "description", thread.description ? jsi::String::createFromUtf8( innerRt, *thread.description) : jsi::Value::null()); jsiThread.setProperty(innerRt, "color", thread.color); jsiThread.setProperty( innerRt, "creationTime", std::to_string(thread.creation_time)); jsiThread.setProperty( innerRt, "parentThreadID", thread.parent_thread_id ? jsi::String::createFromUtf8( innerRt, *thread.parent_thread_id) : jsi::Value::null()); jsiThread.setProperty( innerRt, "containingThreadID", thread.containing_thread_id ? jsi::String::createFromUtf8( innerRt, *thread.containing_thread_id) : jsi::Value::null()); jsiThread.setProperty( innerRt, "community", thread.community ? jsi::String::createFromUtf8(innerRt, *thread.community) : jsi::Value::null()); jsiThread.setProperty(innerRt, "members", thread.members); jsiThread.setProperty(innerRt, "roles", thread.roles); jsiThread.setProperty( innerRt, "currentUser", thread.current_user); jsiThread.setProperty( innerRt, "sourceMessageID", thread.source_message_id ? jsi::String::createFromUtf8( innerRt, *thread.source_message_id) : jsi::Value::null()); jsiThread.setProperty( innerRt, "repliesCount", thread.replies_count); jsiThreads.setValueAtIndex(innerRt, writeIdx++, jsiThread); } promise->resolve(std::move(jsiThreads)); }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); }; jsi::Array CommCoreModule::getAllThreadsSync(jsi::Runtime &rt) { auto threadsVector = this->runSyncOrThrowJSError>( rt, []() { return DatabaseManager::getQueryExecutor().getAllThreads(); }); size_t numThreads{threadsVector.size()}; jsi::Array jsiThreads = jsi::Array(rt, numThreads); size_t writeIdx = 0; for (const Thread &thread : threadsVector) { jsi::Object jsiThread = jsi::Object(rt); jsiThread.setProperty(rt, "id", thread.id); jsiThread.setProperty(rt, "type", thread.type); jsiThread.setProperty( rt, "name", thread.name ? jsi::String::createFromUtf8(rt, *thread.name) : jsi::Value::null()); jsiThread.setProperty( rt, "description", thread.description ? jsi::String::createFromUtf8(rt, *thread.description) : jsi::Value::null()); jsiThread.setProperty(rt, "color", thread.color); jsiThread.setProperty( rt, "creationTime", std::to_string(thread.creation_time)); jsiThread.setProperty( rt, "parentThreadID", thread.parent_thread_id ? jsi::String::createFromUtf8(rt, *thread.parent_thread_id) : jsi::Value::null()); jsiThread.setProperty( rt, "containingThreadID", thread.containing_thread_id ? jsi::String::createFromUtf8(rt, *thread.containing_thread_id) : jsi::Value::null()); jsiThread.setProperty( rt, "community", thread.community ? jsi::String::createFromUtf8(rt, *thread.community) : jsi::Value::null()); jsiThread.setProperty(rt, "members", thread.members); jsiThread.setProperty(rt, "roles", thread.roles); jsiThread.setProperty(rt, "currentUser", thread.current_user); jsiThread.setProperty( rt, "sourceMessageID", thread.source_message_id ? jsi::String::createFromUtf8(rt, *thread.source_message_id) : jsi::Value::null()); jsiThread.setProperty(rt, "repliesCount", thread.replies_count); jsiThreads.setValueAtIndex(rt, writeIdx++, jsiThread); } return jsiThreads; } std::vector> createThreadStoreOperations(jsi::Runtime &rt, const jsi::Array &operations) { std::vector> threadStoreOps; for (size_t idx = 0; idx < operations.size(rt); idx++) { jsi::Object op = operations.getValueAtIndex(rt, idx).asObject(rt); std::string opType = op.getProperty(rt, "type").asString(rt).utf8(rt); if (opType == REMOVE_OPERATION) { std::vector threadIDsToRemove; jsi::Object payloadObj = op.getProperty(rt, "payload").asObject(rt); jsi::Array threadIDs = payloadObj.getProperty(rt, "ids").asObject(rt).asArray(rt); for (int threadIdx = 0; threadIdx < threadIDs.size(rt); threadIdx++) { threadIDsToRemove.push_back( threadIDs.getValueAtIndex(rt, threadIdx).asString(rt).utf8(rt)); } threadStoreOps.push_back(std::make_unique( std::move(threadIDsToRemove))); } else if (opType == REMOVE_ALL_OPERATION) { threadStoreOps.push_back(std::make_unique()); } else if (opType == REPLACE_OPERATION) { jsi::Object threadObj = op.getProperty(rt, "payload").asObject(rt); std::string threadID = threadObj.getProperty(rt, "id").asString(rt).utf8(rt); int type = std::lround(threadObj.getProperty(rt, "type").asNumber()); jsi::Value maybeName = threadObj.getProperty(rt, "name"); std::unique_ptr name = maybeName.isString() ? std::make_unique(maybeName.asString(rt).utf8(rt)) : nullptr; jsi::Value maybeDescription = threadObj.getProperty(rt, "description"); std::unique_ptr description = maybeDescription.isString() ? std::make_unique( maybeDescription.asString(rt).utf8(rt)) : nullptr; std::string color = threadObj.getProperty(rt, "color").asString(rt).utf8(rt); int64_t creationTime = std::stoll( threadObj.getProperty(rt, "creationTime").asString(rt).utf8(rt)); jsi::Value maybeParentThreadID = threadObj.getProperty(rt, "parentThreadID"); std::unique_ptr parentThreadID = maybeParentThreadID.isString() ? std::make_unique( maybeParentThreadID.asString(rt).utf8(rt)) : nullptr; jsi::Value maybeContainingThreadID = threadObj.getProperty(rt, "containingThreadID"); std::unique_ptr containingThreadID = maybeContainingThreadID.isString() ? std::make_unique( maybeContainingThreadID.asString(rt).utf8(rt)) : nullptr; jsi::Value maybeCommunity = threadObj.getProperty(rt, "community"); std::unique_ptr community = maybeCommunity.isString() ? std::make_unique(maybeCommunity.asString(rt).utf8(rt)) : nullptr; std::string members = threadObj.getProperty(rt, "members").asString(rt).utf8(rt); std::string roles = threadObj.getProperty(rt, "roles").asString(rt).utf8(rt); std::string currentUser = threadObj.getProperty(rt, "currentUser").asString(rt).utf8(rt); jsi::Value maybeSourceMessageID = threadObj.getProperty(rt, "sourceMessageID"); std::unique_ptr sourceMessageID = maybeSourceMessageID.isString() ? std::make_unique( maybeSourceMessageID.asString(rt).utf8(rt)) : nullptr; int repliesCount = std::lround(threadObj.getProperty(rt, "repliesCount").asNumber()); Thread thread{ threadID, type, std::move(name), std::move(description), color, creationTime, std::move(parentThreadID), std::move(containingThreadID), std::move(community), members, roles, currentUser, std::move(sourceMessageID), repliesCount}; threadStoreOps.push_back( std::make_unique(std::move(thread))); } else { throw std::runtime_error("unsupported operation: " + opType); } }; return threadStoreOps; } jsi::Value CommCoreModule::processThreadStoreOperations( jsi::Runtime &rt, const jsi::Array &operations) { std::string operationsError; std::shared_ptr>> threadStoreOpsPtr; try { auto threadStoreOps = createThreadStoreOperations(rt, operations); threadStoreOpsPtr = std::make_shared< std::vector>>( std::move(threadStoreOps)); } catch (std::runtime_error &e) { operationsError = e.what(); } return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=]() { std::string error = operationsError; if (!error.size()) { try { DatabaseManager::getQueryExecutor().beginTransaction(); for (const auto &operation : *threadStoreOpsPtr) { operation->execute(); } DatabaseManager::getQueryExecutor().commitTransaction(); } catch (std::system_error &e) { error = e.what(); DatabaseManager::getQueryExecutor().rollbackTransaction(); } } this->jsInvoker_->invokeAsync([=]() { if (error.size()) { promise->reject(error); } else { promise->resolve(jsi::Value::undefined()); } }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } void CommCoreModule::processThreadStoreOperationsSync( jsi::Runtime &rt, const jsi::Array &operations) { std::vector> threadStoreOps; try { threadStoreOps = createThreadStoreOperations(rt, operations); } catch (const std::exception &e) { throw jsi::JSError(rt, e.what()); } this->runSyncOrThrowJSError(rt, [&threadStoreOps]() { try { DatabaseManager::getQueryExecutor().beginTransaction(); for (const auto &operation : threadStoreOps) { operation->execute(); } DatabaseManager::getQueryExecutor().commitTransaction(); } catch (const std::exception &e) { DatabaseManager::getQueryExecutor().rollbackTransaction(); throw e; } }); } jsi::Value CommCoreModule::initializeCryptoAccount( jsi::Runtime &rt, const jsi::String &userId) { std::string userIdStr = userId.utf8(rt); folly::Optional storedSecretKey = this->secureStore.get(this->secureStoreAccountDataKey); if (!storedSecretKey.hasValue()) { storedSecretKey = crypto::Tools::generateRandomString(64); this->secureStore.set( this->secureStoreAccountDataKey, storedSecretKey.value()); } return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=]() { crypto::Persist persist; std::string error; try { folly::Optional accountData = DatabaseManager::getQueryExecutor().getOlmPersistAccountData(); if (accountData.hasValue()) { persist.account = crypto::OlmBuffer(accountData->begin(), accountData->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()); persist.sessions.insert(std::make_pair( sessionsDataItem.target_user_id, sessionDataBuffer)); } } } catch (std::system_error &e) { error = e.what(); } this->cryptoThread->scheduleTask([=]() { std::string error; this->cryptoModule.reset(new crypto::CryptoModule( userIdStr, storedSecretKey.value(), persist)); if (persist.isEmpty()) { crypto::Persist newPersist = this->cryptoModule->storeAsB64(storedSecretKey.value()); GlobalDBSingleton::instance.scheduleOrRunCancellable( [=]() { std::string error; try { DatabaseManager::getQueryExecutor().storeOlmPersistData( newPersist); } catch (std::system_error &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=]() { if (error.size()) { promise->reject(error); return; } promise->resolve(jsi::Value::undefined()); }); }, promise, this->jsInvoker_); } else { this->cryptoModule->restoreFromB64( storedSecretKey.value(), persist); this->jsInvoker_->invokeAsync([=]() { if (error.size()) { promise->reject(error); return; } 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 result; if (this->cryptoModule == nullptr) { error = "user has not been initialized"; } else { result = this->cryptoModule->getIdentityKeys(); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } promise->resolve(jsi::String::createFromUtf8(innerRt, result)); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::getUserOneTimeKeys(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; std::string result; if (this->cryptoModule == nullptr) { error = "user has not been initialized"; } else { result = this->cryptoModule->getOneTimeKeys(); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } promise->resolve(jsi::String::createFromUtf8(innerRt, result)); }); }; this->cryptoThread->scheduleTask(job); }); } CommCoreModule::CommCoreModule( std::shared_ptr jsInvoker) : facebook::react::CommCoreModuleSchemaCxxSpecJSI(jsInvoker), cryptoThread(std::make_unique("crypto")) { GlobalDBSingleton::instance.enableMultithreading(); } double CommCoreModule::getCodeVersion(jsi::Runtime &rt) { return this->codeVersion; } jsi::Value CommCoreModule::setNotifyToken(jsi::Runtime &rt, const 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::setCurrentUserID(jsi::Runtime &rt, const 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().setCurrentUserID(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::getCurrentUserID(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().getCurrentUserID(); } 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::setDeviceID(jsi::Runtime &rt, const jsi::String &deviceType) { std::string type = deviceType.utf8(rt); std::string deviceID; std::string deviceIDGenerationError; try { deviceID = DeviceIDGenerator::generateDeviceID(type); } catch (std::invalid_argument &e) { deviceIDGenerationError = "setDeviceID: incorrect function argument. Must be one of: KEYSERVER, " "WEB, MOBILE."; } return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [this, &innerRt, promise, deviceIDGenerationError, deviceID]() { std::string error = deviceIDGenerationError; if (!error.size()) { try { DatabaseManager::getQueryExecutor().setDeviceID(deviceID); } catch (const std::exception &e) { error = e.what(); } } this->jsInvoker_->invokeAsync([&innerRt, promise, error, deviceID]() { if (error.size()) { promise->reject(error); } else { promise->resolve(jsi::String::createFromUtf8(innerRt, deviceID)); } }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } jsi::Value CommCoreModule::getDeviceID(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().getDeviceID(); } 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 { DatabaseManager::getQueryExecutor().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); }); } } // namespace comm diff --git a/native/cpp/CommonCpp/NativeModules/CommCoreModule.h b/native/cpp/CommonCpp/NativeModules/CommCoreModule.h index 3be2c742b..ccc6ff181 100644 --- a/native/cpp/CommonCpp/NativeModules/CommCoreModule.h +++ b/native/cpp/CommonCpp/NativeModules/CommCoreModule.h @@ -1,72 +1,75 @@ #pragma once #include "../CryptoTools/CryptoModule.h" #include "../Tools/CommSecureStore.h" #include "../Tools/WorkerThread.h" #include "../_generated/NativeModules.h" #include #include #include namespace comm { namespace jsi = facebook::jsi; class CommCoreModule : public facebook::react::CommCoreModuleSchemaCxxSpecJSI { const int codeVersion{158}; std::unique_ptr cryptoThread; CommSecureStore secureStore; const std::string secureStoreAccountDataKey = "cryptoAccountDataKey"; std::unique_ptr cryptoModule; template T runSyncOrThrowJSError(jsi::Runtime &rt, std::function task); jsi::Value getDraft(jsi::Runtime &rt, const jsi::String &key) override; - jsi::Value updateDraft(jsi::Runtime &rt, const jsi::Object &draft) override; + jsi::Value updateDraft( + jsi::Runtime &rt, + const jsi::String &key, + const jsi::String &text) override; jsi::Value moveDraft( jsi::Runtime &rt, const jsi::String &oldKey, const jsi::String &newKey) override; jsi::Value getAllDrafts(jsi::Runtime &rt) override; jsi::Value removeAllDrafts(jsi::Runtime &rt) override; jsi::Value getAllMessages(jsi::Runtime &rt) override; jsi::Array getAllMessagesSync(jsi::Runtime &rt) override; jsi::Value processDraftStoreOperations( jsi::Runtime &rt, const jsi::Array &operations) override; jsi::Value processMessageStoreOperations( jsi::Runtime &rt, const jsi::Array &operations) override; void processMessageStoreOperationsSync( jsi::Runtime &rt, const jsi::Array &operations) override; jsi::Value getAllThreads(jsi::Runtime &rt) override; jsi::Array getAllThreadsSync(jsi::Runtime &rt) override; jsi::Value processThreadStoreOperations( jsi::Runtime &rt, const jsi::Array &operations) override; void processThreadStoreOperationsSync( jsi::Runtime &rt, const jsi::Array &operations) override; jsi::Value initializeCryptoAccount(jsi::Runtime &rt, const jsi::String &userId) override; jsi::Value getUserPublicKey(jsi::Runtime &rt) override; jsi::Value getUserOneTimeKeys(jsi::Runtime &rt) override; double getCodeVersion(jsi::Runtime &rt) override; jsi::Value setNotifyToken(jsi::Runtime &rt, const jsi::String &token) override; jsi::Value clearNotifyToken(jsi::Runtime &rt) override; jsi::Value setCurrentUserID(jsi::Runtime &rt, const jsi::String &userID) override; jsi::Value getCurrentUserID(jsi::Runtime &rt) override; jsi::Value setDeviceID(jsi::Runtime &rt, const jsi::String &deviceType) override; jsi::Value getDeviceID(jsi::Runtime &rt) override; jsi::Value clearSensitiveData(jsi::Runtime &rt) override; public: CommCoreModule(std::shared_ptr jsInvoker); }; } // namespace comm diff --git a/native/cpp/CommonCpp/_generated/NativeModules.cpp b/native/cpp/CommonCpp/_generated/NativeModules.cpp index 16b1e6c34..6a65809e4 100644 --- a/native/cpp/CommonCpp/_generated/NativeModules.cpp +++ b/native/cpp/CommonCpp/_generated/NativeModules.cpp @@ -1,124 +1,124 @@ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @generated by codegen project: GenerateModuleH.js */ #include "NativeModules.h" namespace facebook { namespace react { static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getDraft(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->getDraft(rt, args[0].getString(rt)); } static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_updateDraft(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { - return static_cast(&turboModule)->updateDraft(rt, args[0].getObject(rt)); + return static_cast(&turboModule)->updateDraft(rt, args[0].getString(rt), args[1].getString(rt)); } static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_moveDraft(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->moveDraft(rt, args[0].getString(rt), args[1].getString(rt)); } static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getAllDrafts(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->getAllDrafts(rt); } static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_removeAllDrafts(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->removeAllDrafts(rt); } static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getAllMessages(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->getAllMessages(rt); } static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getAllMessagesSync(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->getAllMessagesSync(rt); } static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_processDraftStoreOperations(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->processDraftStoreOperations(rt, args[0].getObject(rt).getArray(rt)); } static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_processMessageStoreOperations(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->processMessageStoreOperations(rt, args[0].getObject(rt).getArray(rt)); } static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_processMessageStoreOperationsSync(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { static_cast(&turboModule)->processMessageStoreOperationsSync(rt, args[0].getObject(rt).getArray(rt)); return jsi::Value::undefined(); } static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getAllThreads(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->getAllThreads(rt); } static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getAllThreadsSync(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->getAllThreadsSync(rt); } static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_processThreadStoreOperations(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->processThreadStoreOperations(rt, args[0].getObject(rt).getArray(rt)); } static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_processThreadStoreOperationsSync(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { static_cast(&turboModule)->processThreadStoreOperationsSync(rt, args[0].getObject(rt).getArray(rt)); return jsi::Value::undefined(); } static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_initializeCryptoAccount(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->initializeCryptoAccount(rt, args[0].getString(rt)); } static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getUserPublicKey(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->getUserPublicKey(rt); } static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getUserOneTimeKeys(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->getUserOneTimeKeys(rt); } static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getCodeVersion(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->getCodeVersion(rt); } static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_setNotifyToken(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->setNotifyToken(rt, args[0].getString(rt)); } static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_clearNotifyToken(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->clearNotifyToken(rt); } static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_setCurrentUserID(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->setCurrentUserID(rt, args[0].getString(rt)); } static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getCurrentUserID(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->getCurrentUserID(rt); } static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_setDeviceID(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->setDeviceID(rt, args[0].getString(rt)); } static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getDeviceID(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->getDeviceID(rt); } static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_clearSensitiveData(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->clearSensitiveData(rt); } CommCoreModuleSchemaCxxSpecJSI::CommCoreModuleSchemaCxxSpecJSI(std::shared_ptr jsInvoker) : TurboModule("CommTurboModule", jsInvoker) { methodMap_["getDraft"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getDraft}; - methodMap_["updateDraft"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_updateDraft}; + methodMap_["updateDraft"] = MethodMetadata {2, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_updateDraft}; methodMap_["moveDraft"] = MethodMetadata {2, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_moveDraft}; methodMap_["getAllDrafts"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getAllDrafts}; methodMap_["removeAllDrafts"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_removeAllDrafts}; methodMap_["getAllMessages"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getAllMessages}; methodMap_["getAllMessagesSync"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getAllMessagesSync}; methodMap_["processDraftStoreOperations"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_processDraftStoreOperations}; methodMap_["processMessageStoreOperations"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_processMessageStoreOperations}; methodMap_["processMessageStoreOperationsSync"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_processMessageStoreOperationsSync}; methodMap_["getAllThreads"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getAllThreads}; methodMap_["getAllThreadsSync"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getAllThreadsSync}; methodMap_["processThreadStoreOperations"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_processThreadStoreOperations}; methodMap_["processThreadStoreOperationsSync"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_processThreadStoreOperationsSync}; methodMap_["initializeCryptoAccount"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_initializeCryptoAccount}; methodMap_["getUserPublicKey"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getUserPublicKey}; methodMap_["getUserOneTimeKeys"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getUserOneTimeKeys}; methodMap_["getCodeVersion"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getCodeVersion}; methodMap_["setNotifyToken"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_setNotifyToken}; methodMap_["clearNotifyToken"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_clearNotifyToken}; methodMap_["setCurrentUserID"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_setCurrentUserID}; methodMap_["getCurrentUserID"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getCurrentUserID}; methodMap_["setDeviceID"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_setDeviceID}; methodMap_["getDeviceID"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getDeviceID}; methodMap_["clearSensitiveData"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_clearSensitiveData}; } } // namespace react } // namespace facebook diff --git a/native/cpp/CommonCpp/_generated/NativeModules.h b/native/cpp/CommonCpp/_generated/NativeModules.h index 99e02fd65..cbcbf8297 100644 --- a/native/cpp/CommonCpp/_generated/NativeModules.h +++ b/native/cpp/CommonCpp/_generated/NativeModules.h @@ -1,50 +1,50 @@ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @generated by codegen project: GenerateModuleH.js */ #pragma once #include namespace facebook { namespace react { class JSI_EXPORT CommCoreModuleSchemaCxxSpecJSI : public TurboModule { protected: CommCoreModuleSchemaCxxSpecJSI(std::shared_ptr jsInvoker); public: virtual jsi::Value getDraft(jsi::Runtime &rt, const jsi::String &key) = 0; -virtual jsi::Value updateDraft(jsi::Runtime &rt, const jsi::Object &draft) = 0; +virtual jsi::Value updateDraft(jsi::Runtime &rt, const jsi::String &key, const jsi::String &text) = 0; virtual jsi::Value moveDraft(jsi::Runtime &rt, const jsi::String &oldKey, const jsi::String &newKey) = 0; virtual jsi::Value getAllDrafts(jsi::Runtime &rt) = 0; virtual jsi::Value removeAllDrafts(jsi::Runtime &rt) = 0; virtual jsi::Value getAllMessages(jsi::Runtime &rt) = 0; virtual jsi::Array getAllMessagesSync(jsi::Runtime &rt) = 0; virtual jsi::Value processDraftStoreOperations(jsi::Runtime &rt, const jsi::Array &operations) = 0; virtual jsi::Value processMessageStoreOperations(jsi::Runtime &rt, const jsi::Array &operations) = 0; virtual void processMessageStoreOperationsSync(jsi::Runtime &rt, const jsi::Array &operations) = 0; virtual jsi::Value getAllThreads(jsi::Runtime &rt) = 0; virtual jsi::Array getAllThreadsSync(jsi::Runtime &rt) = 0; virtual jsi::Value processThreadStoreOperations(jsi::Runtime &rt, const jsi::Array &operations) = 0; virtual void processThreadStoreOperationsSync(jsi::Runtime &rt, const jsi::Array &operations) = 0; virtual jsi::Value initializeCryptoAccount(jsi::Runtime &rt, const jsi::String &userId) = 0; virtual jsi::Value getUserPublicKey(jsi::Runtime &rt) = 0; virtual jsi::Value getUserOneTimeKeys(jsi::Runtime &rt) = 0; virtual double getCodeVersion(jsi::Runtime &rt) = 0; virtual jsi::Value setNotifyToken(jsi::Runtime &rt, const jsi::String &token) = 0; virtual jsi::Value clearNotifyToken(jsi::Runtime &rt) = 0; virtual jsi::Value setCurrentUserID(jsi::Runtime &rt, const jsi::String &userID) = 0; virtual jsi::Value getCurrentUserID(jsi::Runtime &rt) = 0; virtual jsi::Value setDeviceID(jsi::Runtime &rt, const jsi::String &deviceType) = 0; virtual jsi::Value getDeviceID(jsi::Runtime &rt) = 0; virtual jsi::Value clearSensitiveData(jsi::Runtime &rt) = 0; }; } // namespace react } // namespace facebook diff --git a/native/redux/persist.js b/native/redux/persist.js index 1a2423df4..e3cc7a2aa 100644 --- a/native/redux/persist.js +++ b/native/redux/persist.js @@ -1,515 +1,512 @@ // @flow import AsyncStorage from '@react-native-async-storage/async-storage'; import invariant from 'invariant'; import { Platform } from 'react-native'; import Orientation from 'react-native-orientation-locker'; import { createMigrate, createTransform } from 'redux-persist'; import type { Transform } from 'redux-persist/es/types'; import { highestLocalIDSelector } from 'lib/selectors/local-id-selectors'; import { inconsistencyResponsesToReports } from 'lib/shared/report-utils'; import { getContainingThreadID, getCommunity } from 'lib/shared/thread-utils'; import { unshimMessageStore, unshimFunc } from 'lib/shared/unshim-utils'; import { defaultEnabledApps } from 'lib/types/enabled-apps'; import { defaultCalendarFilters } from 'lib/types/filter-types'; import { type LocalMessageInfo, type MessageStore, messageTypes, type ClientDBMessageStoreOperation, } from 'lib/types/message-types'; import type { ClientDBMessageInfo } from 'lib/types/message-types'; import { defaultConnectionInfo } from 'lib/types/socket-types'; import { translateClientDBMessageInfoToRawMessageInfo, translateRawMessageInfoToClientDBMessageInfo, } from 'lib/utils/message-ops-utils'; import { convertThreadStoreOperationsToClientDBOperations } from 'lib/utils/thread-ops-utils'; import { commCoreModule } from '../native-modules'; import { defaultNotifPermissionAlertInfo } from '../push/alerts'; import { defaultDeviceCameraInfo } from '../types/camera'; import { defaultGlobalThemeInfo } from '../types/themes'; import { isTaskCancelledError } from '../utils/error-handling'; import { migrateThreadStoreForEditThreadPermissions } from './edit-thread-permission-migration'; import type { AppState } from './state-types'; const migrations = { [1]: (state: AppState) => ({ ...state, notifPermissionAlertInfo: defaultNotifPermissionAlertInfo, }), [2]: (state: AppState) => ({ ...state, messageSentFromRoute: [], }), [3]: state => ({ currentUserInfo: state.currentUserInfo, entryStore: state.entryStore, threadInfos: state.threadInfos, userInfos: state.userInfos, messageStore: { ...state.messageStore, currentAsOf: state.currentAsOf, }, updatesCurrentAsOf: state.currentAsOf, cookie: state.cookie, deviceToken: state.deviceToken, urlPrefix: state.urlPrefix, customServer: state.customServer, threadIDsToNotifIDs: state.threadIDsToNotifIDs, notifPermissionAlertInfo: state.notifPermissionAlertInfo, messageSentFromRoute: state.messageSentFromRoute, _persist: state._persist, }), [4]: (state: AppState) => ({ ...state, pingTimestamps: undefined, activeServerRequests: undefined, }), [5]: (state: AppState) => ({ ...state, calendarFilters: defaultCalendarFilters, }), [6]: state => ({ ...state, threadInfos: undefined, threadStore: { threadInfos: state.threadInfos, inconsistencyResponses: [], }, }), [7]: state => ({ ...state, lastUserInteraction: undefined, sessionID: undefined, entryStore: { ...state.entryStore, inconsistencyResponses: [], }, }), [8]: (state: AppState) => ({ ...state, pingTimestamps: undefined, activeServerRequests: undefined, connection: defaultConnectionInfo(Platform.OS), watchedThreadIDs: [], entryStore: { ...state.entryStore, actualizedCalendarQuery: undefined, }, }), [9]: (state: AppState) => ({ ...state, connection: { ...state.connection, lateResponses: [], }, }), [10]: (state: AppState) => ({ ...state, nextLocalID: highestLocalIDSelector(state) + 1, connection: { ...state.connection, showDisconnectedBar: false, }, messageStore: { ...state.messageStore, local: {}, }, }), [11]: (state: AppState) => ({ ...state, messageStore: unshimMessageStore(state.messageStore, [messageTypes.IMAGES]), }), [12]: (state: AppState) => ({ ...state, globalThemeInfo: defaultGlobalThemeInfo, }), [13]: (state: AppState) => ({ ...state, deviceCameraInfo: defaultDeviceCameraInfo, deviceOrientation: Orientation.getInitialOrientation(), }), [14]: (state: AppState) => state, [15]: state => ({ ...state, threadStore: { ...state.threadStore, inconsistencyReports: inconsistencyResponsesToReports( state.threadStore.inconsistencyResponses, ), inconsistencyResponses: undefined, }, entryStore: { ...state.entryStore, inconsistencyReports: inconsistencyResponsesToReports( state.entryStore.inconsistencyResponses, ), inconsistencyResponses: undefined, }, queuedReports: [], }), [16]: state => { const result = { ...state, messageSentFromRoute: undefined, dataLoaded: !!state.currentUserInfo && !state.currentUserInfo.anonymous, }; if (state.navInfo) { result.navInfo = { ...state.navInfo, navigationState: undefined, }; } return result; }, [17]: state => ({ ...state, userInfos: undefined, userStore: { userInfos: state.userInfos, inconsistencyResponses: [], }, }), [18]: state => ({ ...state, userStore: { userInfos: state.userStore.userInfos, inconsistencyReports: [], }, }), [19]: state => { const threadInfos = {}; for (const threadID in state.threadStore.threadInfos) { const threadInfo = state.threadStore.threadInfos[threadID]; const { visibilityRules, ...rest } = threadInfo; threadInfos[threadID] = rest; } return { ...state, threadStore: { ...state.threadStore, threadInfos, }, }; }, [20]: (state: AppState) => ({ ...state, messageStore: unshimMessageStore(state.messageStore, [ messageTypes.UPDATE_RELATIONSHIP, ]), }), [21]: (state: AppState) => ({ ...state, messageStore: unshimMessageStore(state.messageStore, [ messageTypes.CREATE_SIDEBAR, messageTypes.SIDEBAR_SOURCE, ]), }), [22]: state => { for (const key in state.drafts) { const value = state.drafts[key]; try { - commCoreModule.updateDraft({ - key, - text: value, - }); + commCoreModule.updateDraft(key, value); } catch (e) { if (!isTaskCancelledError(e)) { throw e; } } } return { ...state, drafts: undefined, }; }, [23]: state => ({ ...state, globalThemeInfo: defaultGlobalThemeInfo, }), [24]: state => ({ ...state, enabledApps: defaultEnabledApps, }), [25]: state => ({ ...state, crashReportsEnabled: __DEV__, }), [26]: state => { const { currentUserInfo } = state; if (currentUserInfo.anonymous) { return state; } return { ...state, crashReportsEnabled: undefined, currentUserInfo: { id: currentUserInfo.id, username: currentUserInfo.username, }, enabledReports: { crashReports: __DEV__, inconsistencyReports: __DEV__, mediaReports: __DEV__, }, }; }, [27]: state => ({ ...state, queuedReports: undefined, enabledReports: undefined, threadStore: { ...state.threadStore, inconsistencyReports: undefined, }, entryStore: { ...state.entryStore, inconsistencyReports: undefined, }, reportStore: { enabledReports: { crashReports: __DEV__, inconsistencyReports: __DEV__, mediaReports: __DEV__, }, queuedReports: [ ...state.entryStore.inconsistencyReports, ...state.threadStore.inconsistencyReports, ...state.queuedReports, ], }, }), [28]: state => { const threadParentToChildren = {}; for (const threadID in state.threadStore.threadInfos) { const threadInfo = state.threadStore.threadInfos[threadID]; const parentThreadInfo = threadInfo.parentThreadID ? state.threadStore.threadInfos[threadInfo.parentThreadID] : null; const parentIndex = parentThreadInfo ? parentThreadInfo.id : '-1'; if (!threadParentToChildren[parentIndex]) { threadParentToChildren[parentIndex] = []; } threadParentToChildren[parentIndex].push(threadID); } const rootIDs = threadParentToChildren['-1']; if (!rootIDs) { // This should never happen, but if it somehow does we'll let the state // check mechanism resolve it... return state; } const threadInfos = {}; const stack = [...rootIDs]; while (stack.length > 0) { const threadID = stack.shift(); const threadInfo = state.threadStore.threadInfos[threadID]; const parentThreadInfo = threadInfo.parentThreadID ? threadInfos[threadInfo.parentThreadID] : null; threadInfos[threadID] = { ...threadInfo, containingThreadID: getContainingThreadID( parentThreadInfo, threadInfo.type, ), community: getCommunity(parentThreadInfo), }; const children = threadParentToChildren[threadID]; if (children) { stack.push(...children); } } return { ...state, threadStore: { ...state.threadStore, threadInfos } }; }, [29]: (state: AppState) => { const updatedThreadInfos = migrateThreadStoreForEditThreadPermissions( state.threadStore.threadInfos, ); return { ...state, threadStore: { ...state.threadStore, threadInfos: updatedThreadInfos, }, }; }, [30]: (state: AppState) => { const threadInfos = state.threadStore.threadInfos; const operations = [ { type: 'remove_all', }, ...Object.keys(threadInfos).map((id: string) => ({ type: 'replace', payload: { id, threadInfo: threadInfos[id] }, })), ]; try { commCoreModule.processThreadStoreOperationsSync( convertThreadStoreOperationsToClientDBOperations(operations), ); } catch (exception) { console.log(exception); if (isTaskCancelledError(exception)) { return state; } return { ...state, cookie: null }; } return state; }, [31]: (state: AppState) => { const messages = state.messageStore.messages; const operations: $ReadOnlyArray = [ { type: 'remove_all', }, ...Object.keys(messages).map((id: string) => ({ type: 'replace', payload: translateRawMessageInfoToClientDBMessageInfo(messages[id]), })), ]; try { commCoreModule.processMessageStoreOperationsSync(operations); } catch (exception) { console.log(exception); if (isTaskCancelledError(exception)) { return state; } return { ...state, cookie: null }; } return state; }, [32]: (state: AppState) => { // 1. Get messages from SQLite `messages` table. const clientDBMessageInfos = commCoreModule.getAllMessagesSync(); // 2. Translate `ClientDBMessageInfo`s to `RawMessageInfo`s. const rawMessageInfos = clientDBMessageInfos.map( translateClientDBMessageInfoToRawMessageInfo, ); // 3. "Unshim" translated `RawMessageInfo`s. const unshimmedRawMessageInfos = rawMessageInfos.map(messageInfo => unshimFunc(messageInfo, new Set([messageTypes.MULTIMEDIA])), ); // 4. Translate unshimmed `RawMessageInfo`s back to `ClientDBMessageInfo`s. const unshimmedClientDBMessageInfos = unshimmedRawMessageInfos.map( translateRawMessageInfoToClientDBMessageInfo, ); // 5. Construct `ClientDBMessageStoreOperation`s to clear SQLite `messages` // table and repopulate with unshimmed `ClientDBMessageInfo`s. const operations: $ReadOnlyArray = [ { type: 'remove_all', }, ...unshimmedClientDBMessageInfos.map((message: ClientDBMessageInfo) => ({ type: 'replace', payload: message, })), ]; // 6. Try processing `ClientDBMessageStoreOperation`s and log out if // `processMessageStoreOperationsSync(...)` throws an exception. try { commCoreModule.processMessageStoreOperationsSync(operations); } catch (exception) { console.log(exception); return { ...state, cookie: null }; } return state; }, }; // After migration 31, we'll no longer want to persist `messageStore.messages` // via redux-persist. However, we DO want to continue persisting everything in // `messageStore` EXCEPT for `messages`. The `blacklist` property in // `persistConfig` allows us to specify top-level keys that shouldn't be // persisted. However, we aren't able to specify nested keys in `blacklist`. // As a result, if we want to prevent nested keys from being persisted we'll // need to use `createTransform(...)` to specify an `inbound` function that // allows us to modify the `state` object before it's passed through // `JSON.stringify(...)` and written to disk. We specify the keys for which // this transformation should be executed in the `whitelist` property of the // `config` object that's passed to `createTransform(...)`. // eslint-disable-next-line no-unused-vars type PersistedThreadMessageInfo = { +startReached: boolean, +lastNavigatedTo: number, +lastPruned: number, }; type PersistedMessageStore = { +local: { +[id: string]: LocalMessageInfo }, +currentAsOf: number, +threads: { +[threadID: string]: PersistedThreadMessageInfo }, }; const messageStoreMessagesBlocklistTransform: Transform = createTransform( (state: MessageStore): PersistedMessageStore => { const { messages, threads, ...messageStoreSansMessages } = state; // We also do not want to persist `messageStore.threads[ID].messageIDs` // because they can be deterministically computed based on messages we have // from SQLite const threadsToPersist = {}; for (const threadID in threads) { const { messageIDs, ...threadsData } = threads[threadID]; threadsToPersist[threadID] = threadsData; } return { ...messageStoreSansMessages, threads: threadsToPersist }; }, (state: PersistedMessageStore): MessageStore => { const { threads: persistedThreads, ...messageStore } = state; const threads = {}; for (const threadID in persistedThreads) { threads[threadID] = { ...persistedThreads[threadID], messageIDs: [] }; } return { ...messageStore, threads, messages: {} }; }, { whitelist: ['messageStore'] }, ); const persistConfig = { key: 'root', storage: AsyncStorage, blacklist: [ 'loadingStatuses', 'lifecycleState', 'dimensions', 'draftStore', 'connectivity', 'deviceOrientation', 'frozen', 'threadStore', 'storeLoaded', ], debug: __DEV__, version: 32, transforms: [messageStoreMessagesBlocklistTransform], migrate: (createMigrate(migrations, { debug: __DEV__ }): any), timeout: ((__DEV__ ? 0 : undefined): number | void), }; const codeVersion: number = commCoreModule.getCodeVersion(); // This local exists to avoid a circular dependency where redux-setup needs to // import all the navigation and screen stuff, but some of those screens want to // access the persistor to purge its state. let storedPersistor = null; function setPersistor(persistor: *) { storedPersistor = persistor; } function getPersistor(): empty { invariant(storedPersistor, 'should be set'); return storedPersistor; } export { persistConfig, codeVersion, setPersistor, getPersistor }; diff --git a/native/schema/CommCoreModuleSchema.js b/native/schema/CommCoreModuleSchema.js index 1940b1b95..9780e3b4b 100644 --- a/native/schema/CommCoreModuleSchema.js +++ b/native/schema/CommCoreModuleSchema.js @@ -1,61 +1,61 @@ // @flow 'use strict'; import { TurboModuleRegistry } from 'react-native'; import type { TurboModule } from 'react-native/Libraries/TurboModule/RCTExport'; import type { ClientDBDraftInfo, ClientDBDraftStoreOperation, } from 'lib/types/draft-types'; import type { ClientDBMessageInfo, ClientDBMessageStoreOperation, } from 'lib/types/message-types'; import type { ClientDBThreadInfo, ClientDBThreadStoreOperation, } from 'lib/types/thread-types'; export interface Spec extends TurboModule { +getDraft: (key: string) => Promise; - +updateDraft: (draft: ClientDBDraftInfo) => Promise; + +updateDraft: (key: string, text: string) => Promise; +moveDraft: (oldKey: string, newKey: string) => Promise; +getAllDrafts: () => Promise<$ReadOnlyArray>; +removeAllDrafts: () => Promise; +getAllMessages: () => Promise<$ReadOnlyArray>; +getAllMessagesSync: () => $ReadOnlyArray; +processDraftStoreOperations: ( operations: $ReadOnlyArray, ) => Promise; +processMessageStoreOperations: ( operations: $ReadOnlyArray, ) => Promise; +processMessageStoreOperationsSync: ( operations: $ReadOnlyArray, ) => void; +getAllThreads: () => Promise<$ReadOnlyArray>; +getAllThreadsSync: () => $ReadOnlyArray; +processThreadStoreOperations: ( operations: $ReadOnlyArray, ) => Promise; +processThreadStoreOperationsSync: ( operations: $ReadOnlyArray, ) => void; +initializeCryptoAccount: (userId: string) => Promise; +getUserPublicKey: () => Promise; +getUserOneTimeKeys: () => Promise; +getCodeVersion: () => number; +setNotifyToken: (token: string) => Promise; +clearNotifyToken: () => Promise; +setCurrentUserID: (userID: string) => Promise; +getCurrentUserID: () => Promise; +setDeviceID: (deviceType: string) => Promise; +getDeviceID: () => Promise; +clearSensitiveData: () => Promise; } export default (TurboModuleRegistry.getEnforcing( 'CommTurboModule', ): Spec);