diff --git a/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp b/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp index c0c826bdf..785b2b502 100644 --- a/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp +++ b/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp @@ -1,1074 +1,1078 @@ #include "CommCoreModule.h" #include "../CryptoTools/DeviceID.h" #include "DatabaseManager.h" #include "DraftStoreOperations.h" #include "InternalModules/GlobalDBSingleton.h" #include "MessageStoreOperations.h" #include "TerminateApp.h" #include "ThreadStoreOperations.h" #include #include #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, 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::Array parseDBDrafts( jsi::Runtime &rt, std::shared_ptr> draftsVectorPtr) { size_t numDrafts = count_if( draftsVectorPtr->begin(), draftsVectorPtr->end(), [](Draft draft) { return !draft.text.empty(); }); jsi::Array jsiDrafts = jsi::Array(rt, numDrafts); size_t writeIndex = 0; for (Draft draft : *draftsVectorPtr) { if (draft.text.empty()) { continue; } auto jsiDraft = jsi::Object(rt); jsiDraft.setProperty(rt, "key", draft.key); jsiDraft.setProperty(rt, "text", draft.text); jsiDrafts.setValueAtIndex(rt, writeIndex++, jsiDraft); } return jsiDrafts; } jsi::Array parseDBMessages( jsi::Runtime &rt, std::shared_ptr>>> messagesVectorPtr) { size_t numMessages = messagesVectorPtr->size(); jsi::Array jsiMessages = jsi::Array(rt, numMessages); size_t writeIndex = 0; for (const auto &[message, media] : *messagesVectorPtr) { 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::Array parseDBThreads( jsi::Runtime &rt, std::shared_ptr> threadsVectorPtr) { size_t numThreads = threadsVectorPtr->size(); jsi::Array jsiThreads = jsi::Array(rt, numThreads); size_t writeIdx = 0; for (const Thread &thread : *threadsVectorPtr) { 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; } 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; try { draftsVector = DatabaseManager::getQueryExecutor().getAllDrafts(); messagesVector = DatabaseManager::getQueryExecutor().getAllMessages(); threadsVector = DatabaseManager::getQueryExecutor().getAllThreads(); } 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)); this->jsInvoker_->invokeAsync([&innerRt, draftsVectorPtr, messagesVectorPtr, threadsVectorPtr, error, promise]() { if (error.size()) { promise->reject(error); return; } jsi::Array jsiDrafts = parseDBDrafts(innerRt, draftsVectorPtr); jsi::Array jsiMessages = parseDBMessages(innerRt, messagesVectorPtr); jsi::Array jsiThreads = parseDBThreads(innerRt, threadsVectorPtr); auto jsiClientDBStore = jsi::Object(innerRt); jsiClientDBStore.setProperty(innerRt, "messages", jsiMessages); jsiClientDBStore.setProperty(innerRt, "threads", jsiThreads); jsiClientDBStore.setProperty(innerRt, "drafts", jsiDrafts); 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 = this->runSyncOrThrowJSError< std::vector>>>(rt, []() { return DatabaseManager::getQueryExecutor().getAllMessages(); }); auto messagesVectorPtr = std::make_shared>>>( std::move(messagesVector)); jsi::Array jsiMessages = parseDBMessages(rt, messagesVectorPtr); return jsiMessages; } 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, 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, 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, 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::Array CommCoreModule::getAllThreadsSync(jsi::Runtime &rt) { auto threadsVector = this->runSyncOrThrowJSError>( rt, []() { return DatabaseManager::getQueryExecutor().getAllThreads(); }); auto threadsVectorPtr = std::make_shared>(std::move(threadsVector)); jsi::Array jsiThreads = parseDBThreads(rt, threadsVectorPtr); 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, 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, 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; } }); } void CommCoreModule::terminate(jsi::Runtime &rt) { TerminateApp::terminate(); } jsi::Value CommCoreModule::initializeCryptoAccount(jsi::Runtime &rt, 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; } folly::dynamic parsed = folly::parseJson(result); auto curve25519{jsi::String::createFromUtf8( innerRt, parsed["curve25519"].asString())}; auto ed25519{jsi::String::createFromUtf8( innerRt, parsed["ed25519"].asString())}; auto jsiClientPublicKeys = jsi::Object(innerRt); jsiClientPublicKeys.setProperty(innerRt, "curve25519", curve25519); jsiClientPublicKeys.setProperty(innerRt, "ed25519", ed25519); promise->resolve(std::move(jsiClientPublicKeys)); }); }; 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, 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, 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, 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::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(); +} + } // namespace comm diff --git a/native/cpp/CommonCpp/NativeModules/CommCoreModule.h b/native/cpp/CommonCpp/NativeModules/CommCoreModule.h index 04475313b..229227a61 100644 --- a/native/cpp/CommonCpp/NativeModules/CommCoreModule.h +++ b/native/cpp/CommonCpp/NativeModules/CommCoreModule.h @@ -1,70 +1,71 @@ #pragma once #include "../CryptoTools/CryptoModule.h" #include "../Tools/CommSecureStore.h" #include "../Tools/WorkerThread.h" #include "../_generated/commJSI.h" #include #include #include namespace comm { namespace jsi = facebook::jsi; class CommCoreModule : public facebook::react::CommCoreModuleSchemaCxxSpecJSI { const int codeVersion{190}; std::unique_ptr cryptoThread; CommSecureStore secureStore; const std::string secureStoreAccountDataKey = "cryptoAccountDataKey"; std::unique_ptr cryptoModule; template T runSyncOrThrowJSError(jsi::Runtime &rt, std::function task); virtual jsi::Value getDraft(jsi::Runtime &rt, jsi::String key) override; virtual jsi::Value updateDraft(jsi::Runtime &rt, jsi::String key, jsi::String text) override; virtual jsi::Value moveDraft(jsi::Runtime &rt, jsi::String oldKey, jsi::String newKey) override; virtual jsi::Value getClientDBStore(jsi::Runtime &rt) override; virtual jsi::Value removeAllDrafts(jsi::Runtime &rt) override; virtual jsi::Array getAllMessagesSync(jsi::Runtime &rt) override; virtual jsi::Value processDraftStoreOperations(jsi::Runtime &rt, jsi::Array operations) override; virtual jsi::Value processMessageStoreOperations( jsi::Runtime &rt, jsi::Array operations) override; virtual void processMessageStoreOperationsSync( jsi::Runtime &rt, jsi::Array operations) override; virtual jsi::Array getAllThreadsSync(jsi::Runtime &rt) override; virtual jsi::Value processThreadStoreOperations( jsi::Runtime &rt, jsi::Array operations) override; virtual void processThreadStoreOperationsSync( jsi::Runtime &rt, jsi::Array operations) override; virtual jsi::Value initializeCryptoAccount(jsi::Runtime &rt, jsi::String userId) override; virtual jsi::Value getUserPublicKey(jsi::Runtime &rt) override; virtual jsi::Value getUserOneTimeKeys(jsi::Runtime &rt) override; virtual void terminate(jsi::Runtime &rt) override; virtual double getCodeVersion(jsi::Runtime &rt) override; virtual jsi::Value setNotifyToken(jsi::Runtime &rt, jsi::String token) override; virtual jsi::Value clearNotifyToken(jsi::Runtime &rt) override; virtual jsi::Value setCurrentUserID(jsi::Runtime &rt, jsi::String userID) override; virtual jsi::Value getCurrentUserID(jsi::Runtime &rt) override; virtual jsi::Value setDeviceID(jsi::Runtime &rt, jsi::String deviceType) override; virtual jsi::Value getDeviceID(jsi::Runtime &rt) override; virtual jsi::Value clearSensitiveData(jsi::Runtime &rt) override; virtual bool checkIfDatabaseNeedsDeletion(jsi::Runtime &rt) override; + virtual void reportDBOperationsFailure(jsi::Runtime &rt) override; public: CommCoreModule(std::shared_ptr jsInvoker); }; } // namespace comm diff --git a/native/cpp/CommonCpp/_generated/commJSI-generated.cpp b/native/cpp/CommonCpp/_generated/commJSI-generated.cpp index 91789106f..5007e3191 100644 --- a/native/cpp/CommonCpp/_generated/commJSI-generated.cpp +++ b/native/cpp/CommonCpp/_generated/commJSI-generated.cpp @@ -1,125 +1,130 @@ /** * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). * * Do not edit this file as changes may cause incorrect behavior and will be lost * once the code is regenerated. * * @generated by codegen project: GenerateModuleH.js */ #include "commJSI.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].asString(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].asString(rt), args[1].asString(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].asString(rt), args[1].asString(rt)); } static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getClientDBStore(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->getClientDBStore(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_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].asObject(rt).asArray(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].asObject(rt).asArray(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].asObject(rt).asArray(rt)); return jsi::Value::undefined(); } 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].asObject(rt).asArray(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].asObject(rt).asArray(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].asString(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_terminate(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { static_cast(&turboModule)->terminate(rt); return jsi::Value::undefined(); } 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].asString(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].asString(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].asString(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); } static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_checkIfDatabaseNeedsDeletion(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->checkIfDatabaseNeedsDeletion(rt); } +static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_reportDBOperationsFailure(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->reportDBOperationsFailure(rt); + return jsi::Value::undefined(); +} CommCoreModuleSchemaCxxSpecJSI::CommCoreModuleSchemaCxxSpecJSI(std::shared_ptr jsInvoker) : TurboModule("CommTurboModule", jsInvoker) { methodMap_["getDraft"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getDraft}; methodMap_["updateDraft"] = MethodMetadata {2, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_updateDraft}; methodMap_["moveDraft"] = MethodMetadata {2, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_moveDraft}; methodMap_["getClientDBStore"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getClientDBStore}; methodMap_["removeAllDrafts"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_removeAllDrafts}; 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_["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_["terminate"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_terminate}; 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}; methodMap_["checkIfDatabaseNeedsDeletion"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_checkIfDatabaseNeedsDeletion}; + methodMap_["reportDBOperationsFailure"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_reportDBOperationsFailure}; } } // namespace react } // namespace facebook diff --git a/native/cpp/CommonCpp/_generated/commJSI.h b/native/cpp/CommonCpp/_generated/commJSI.h index d48e747e8..23370ecff 100644 --- a/native/cpp/CommonCpp/_generated/commJSI.h +++ b/native/cpp/CommonCpp/_generated/commJSI.h @@ -1,278 +1,287 @@ /** * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). * * Do not edit this file as changes may cause incorrect behavior and will be lost * once the code is regenerated. * * @generated by codegen project: GenerateModuleH.js */ #pragma once #include #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, jsi::String key) = 0; virtual jsi::Value updateDraft(jsi::Runtime &rt, jsi::String key, jsi::String text) = 0; virtual jsi::Value moveDraft(jsi::Runtime &rt, jsi::String oldKey, jsi::String newKey) = 0; virtual jsi::Value getClientDBStore(jsi::Runtime &rt) = 0; virtual jsi::Value removeAllDrafts(jsi::Runtime &rt) = 0; virtual jsi::Array getAllMessagesSync(jsi::Runtime &rt) = 0; virtual jsi::Value processDraftStoreOperations(jsi::Runtime &rt, jsi::Array operations) = 0; virtual jsi::Value processMessageStoreOperations(jsi::Runtime &rt, jsi::Array operations) = 0; virtual void processMessageStoreOperationsSync(jsi::Runtime &rt, jsi::Array operations) = 0; virtual jsi::Array getAllThreadsSync(jsi::Runtime &rt) = 0; virtual jsi::Value processThreadStoreOperations(jsi::Runtime &rt, jsi::Array operations) = 0; virtual void processThreadStoreOperationsSync(jsi::Runtime &rt, jsi::Array operations) = 0; virtual jsi::Value initializeCryptoAccount(jsi::Runtime &rt, 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 void terminate(jsi::Runtime &rt) = 0; virtual jsi::Value setNotifyToken(jsi::Runtime &rt, jsi::String token) = 0; virtual jsi::Value clearNotifyToken(jsi::Runtime &rt) = 0; virtual jsi::Value setCurrentUserID(jsi::Runtime &rt, jsi::String userID) = 0; virtual jsi::Value getCurrentUserID(jsi::Runtime &rt) = 0; virtual jsi::Value setDeviceID(jsi::Runtime &rt, jsi::String deviceType) = 0; virtual jsi::Value getDeviceID(jsi::Runtime &rt) = 0; virtual jsi::Value clearSensitiveData(jsi::Runtime &rt) = 0; virtual bool checkIfDatabaseNeedsDeletion(jsi::Runtime &rt) = 0; + virtual void reportDBOperationsFailure(jsi::Runtime &rt) = 0; }; template class JSI_EXPORT CommCoreModuleSchemaCxxSpec : public TurboModule { public: jsi::Value get(jsi::Runtime &rt, const jsi::PropNameID &propName) override { return delegate_.get(rt, propName); } protected: CommCoreModuleSchemaCxxSpec(std::shared_ptr jsInvoker) : TurboModule("CommTurboModule", jsInvoker), delegate_(static_cast(this), jsInvoker) {} private: class Delegate : public CommCoreModuleSchemaCxxSpecJSI { public: Delegate(T *instance, std::shared_ptr jsInvoker) : CommCoreModuleSchemaCxxSpecJSI(std::move(jsInvoker)), instance_(instance) {} jsi::Value getDraft(jsi::Runtime &rt, jsi::String key) override { static_assert( bridging::getParameterCount(&T::getDraft) == 2, "Expected getDraft(...) to have 2 parameters"); return bridging::callFromJs( rt, &T::getDraft, jsInvoker_, instance_, std::move(key)); } jsi::Value updateDraft(jsi::Runtime &rt, jsi::String key, jsi::String text) override { static_assert( bridging::getParameterCount(&T::updateDraft) == 3, "Expected updateDraft(...) to have 3 parameters"); return bridging::callFromJs( rt, &T::updateDraft, jsInvoker_, instance_, std::move(key), std::move(text)); } jsi::Value moveDraft(jsi::Runtime &rt, jsi::String oldKey, jsi::String newKey) override { static_assert( bridging::getParameterCount(&T::moveDraft) == 3, "Expected moveDraft(...) to have 3 parameters"); return bridging::callFromJs( rt, &T::moveDraft, jsInvoker_, instance_, std::move(oldKey), std::move(newKey)); } jsi::Value getClientDBStore(jsi::Runtime &rt) override { static_assert( bridging::getParameterCount(&T::getClientDBStore) == 1, "Expected getClientDBStore(...) to have 1 parameters"); return bridging::callFromJs( rt, &T::getClientDBStore, jsInvoker_, instance_); } jsi::Value removeAllDrafts(jsi::Runtime &rt) override { static_assert( bridging::getParameterCount(&T::removeAllDrafts) == 1, "Expected removeAllDrafts(...) to have 1 parameters"); return bridging::callFromJs( rt, &T::removeAllDrafts, jsInvoker_, instance_); } jsi::Array getAllMessagesSync(jsi::Runtime &rt) override { static_assert( bridging::getParameterCount(&T::getAllMessagesSync) == 1, "Expected getAllMessagesSync(...) to have 1 parameters"); return bridging::callFromJs( rt, &T::getAllMessagesSync, jsInvoker_, instance_); } jsi::Value processDraftStoreOperations(jsi::Runtime &rt, jsi::Array operations) override { static_assert( bridging::getParameterCount(&T::processDraftStoreOperations) == 2, "Expected processDraftStoreOperations(...) to have 2 parameters"); return bridging::callFromJs( rt, &T::processDraftStoreOperations, jsInvoker_, instance_, std::move(operations)); } jsi::Value processMessageStoreOperations(jsi::Runtime &rt, jsi::Array operations) override { static_assert( bridging::getParameterCount(&T::processMessageStoreOperations) == 2, "Expected processMessageStoreOperations(...) to have 2 parameters"); return bridging::callFromJs( rt, &T::processMessageStoreOperations, jsInvoker_, instance_, std::move(operations)); } void processMessageStoreOperationsSync(jsi::Runtime &rt, jsi::Array operations) override { static_assert( bridging::getParameterCount(&T::processMessageStoreOperationsSync) == 2, "Expected processMessageStoreOperationsSync(...) to have 2 parameters"); return bridging::callFromJs( rt, &T::processMessageStoreOperationsSync, jsInvoker_, instance_, std::move(operations)); } jsi::Array getAllThreadsSync(jsi::Runtime &rt) override { static_assert( bridging::getParameterCount(&T::getAllThreadsSync) == 1, "Expected getAllThreadsSync(...) to have 1 parameters"); return bridging::callFromJs( rt, &T::getAllThreadsSync, jsInvoker_, instance_); } jsi::Value processThreadStoreOperations(jsi::Runtime &rt, jsi::Array operations) override { static_assert( bridging::getParameterCount(&T::processThreadStoreOperations) == 2, "Expected processThreadStoreOperations(...) to have 2 parameters"); return bridging::callFromJs( rt, &T::processThreadStoreOperations, jsInvoker_, instance_, std::move(operations)); } void processThreadStoreOperationsSync(jsi::Runtime &rt, jsi::Array operations) override { static_assert( bridging::getParameterCount(&T::processThreadStoreOperationsSync) == 2, "Expected processThreadStoreOperationsSync(...) to have 2 parameters"); return bridging::callFromJs( rt, &T::processThreadStoreOperationsSync, jsInvoker_, instance_, std::move(operations)); } jsi::Value initializeCryptoAccount(jsi::Runtime &rt, jsi::String userId) override { static_assert( bridging::getParameterCount(&T::initializeCryptoAccount) == 2, "Expected initializeCryptoAccount(...) to have 2 parameters"); return bridging::callFromJs( rt, &T::initializeCryptoAccount, jsInvoker_, instance_, std::move(userId)); } jsi::Value getUserPublicKey(jsi::Runtime &rt) override { static_assert( bridging::getParameterCount(&T::getUserPublicKey) == 1, "Expected getUserPublicKey(...) to have 1 parameters"); return bridging::callFromJs( rt, &T::getUserPublicKey, jsInvoker_, instance_); } jsi::Value getUserOneTimeKeys(jsi::Runtime &rt) override { static_assert( bridging::getParameterCount(&T::getUserOneTimeKeys) == 1, "Expected getUserOneTimeKeys(...) to have 1 parameters"); return bridging::callFromJs( rt, &T::getUserOneTimeKeys, jsInvoker_, instance_); } double getCodeVersion(jsi::Runtime &rt) override { static_assert( bridging::getParameterCount(&T::getCodeVersion) == 1, "Expected getCodeVersion(...) to have 1 parameters"); return bridging::callFromJs( rt, &T::getCodeVersion, jsInvoker_, instance_); } void terminate(jsi::Runtime &rt) override { static_assert( bridging::getParameterCount(&T::terminate) == 1, "Expected terminate(...) to have 1 parameters"); return bridging::callFromJs( rt, &T::terminate, jsInvoker_, instance_); } jsi::Value setNotifyToken(jsi::Runtime &rt, jsi::String token) override { static_assert( bridging::getParameterCount(&T::setNotifyToken) == 2, "Expected setNotifyToken(...) to have 2 parameters"); return bridging::callFromJs( rt, &T::setNotifyToken, jsInvoker_, instance_, std::move(token)); } jsi::Value clearNotifyToken(jsi::Runtime &rt) override { static_assert( bridging::getParameterCount(&T::clearNotifyToken) == 1, "Expected clearNotifyToken(...) to have 1 parameters"); return bridging::callFromJs( rt, &T::clearNotifyToken, jsInvoker_, instance_); } jsi::Value setCurrentUserID(jsi::Runtime &rt, jsi::String userID) override { static_assert( bridging::getParameterCount(&T::setCurrentUserID) == 2, "Expected setCurrentUserID(...) to have 2 parameters"); return bridging::callFromJs( rt, &T::setCurrentUserID, jsInvoker_, instance_, std::move(userID)); } jsi::Value getCurrentUserID(jsi::Runtime &rt) override { static_assert( bridging::getParameterCount(&T::getCurrentUserID) == 1, "Expected getCurrentUserID(...) to have 1 parameters"); return bridging::callFromJs( rt, &T::getCurrentUserID, jsInvoker_, instance_); } jsi::Value setDeviceID(jsi::Runtime &rt, jsi::String deviceType) override { static_assert( bridging::getParameterCount(&T::setDeviceID) == 2, "Expected setDeviceID(...) to have 2 parameters"); return bridging::callFromJs( rt, &T::setDeviceID, jsInvoker_, instance_, std::move(deviceType)); } jsi::Value getDeviceID(jsi::Runtime &rt) override { static_assert( bridging::getParameterCount(&T::getDeviceID) == 1, "Expected getDeviceID(...) to have 1 parameters"); return bridging::callFromJs( rt, &T::getDeviceID, jsInvoker_, instance_); } jsi::Value clearSensitiveData(jsi::Runtime &rt) override { static_assert( bridging::getParameterCount(&T::clearSensitiveData) == 1, "Expected clearSensitiveData(...) to have 1 parameters"); return bridging::callFromJs( rt, &T::clearSensitiveData, jsInvoker_, instance_); } bool checkIfDatabaseNeedsDeletion(jsi::Runtime &rt) override { static_assert( bridging::getParameterCount(&T::checkIfDatabaseNeedsDeletion) == 1, "Expected checkIfDatabaseNeedsDeletion(...) to have 1 parameters"); return bridging::callFromJs( rt, &T::checkIfDatabaseNeedsDeletion, jsInvoker_, instance_); } + void reportDBOperationsFailure(jsi::Runtime &rt) override { + static_assert( + bridging::getParameterCount(&T::reportDBOperationsFailure) == 1, + "Expected reportDBOperationsFailure(...) to have 1 parameters"); + + return bridging::callFromJs( + rt, &T::reportDBOperationsFailure, jsInvoker_, instance_); + } private: T *instance_; }; Delegate delegate_; }; } // namespace react } // namespace facebook diff --git a/native/redux/redux-setup.js b/native/redux/redux-setup.js index 71244b8ac..86bc81440 100644 --- a/native/redux/redux-setup.js +++ b/native/redux/redux-setup.js @@ -1,546 +1,549 @@ // @flow import { AppState as NativeAppState, Platform, Alert } from 'react-native'; import Orientation from 'react-native-orientation-locker'; import { createStore, applyMiddleware, type Store, compose } from 'redux'; import { persistStore, persistReducer } from 'redux-persist'; import thunk from 'redux-thunk'; import { setClientDBStoreActionType } from 'lib/actions/client-db-store-actions.js'; import { setDeviceTokenActionTypes } from 'lib/actions/device-actions.js'; import { siweAuthActionTypes } from 'lib/actions/siwe-actions.js'; import { logOutActionTypes, deleteAccountActionTypes, logInActionTypes, } from 'lib/actions/user-actions.js'; import baseReducer from 'lib/reducers/master-reducer.js'; import { processThreadStoreOperations } from 'lib/reducers/thread-reducer.js'; import { invalidSessionDowngrade, invalidSessionRecovery, } from 'lib/shared/account-utils.js'; import { isStaff } from 'lib/shared/user-utils.js'; import { defaultEnabledApps } from 'lib/types/enabled-apps.js'; import { defaultCalendarFilters } from 'lib/types/filter-types.js'; import type { Dispatch, BaseAction } from 'lib/types/redux-types.js'; import { rehydrateActionType } from 'lib/types/redux-types.js'; import type { SetSessionPayload } from 'lib/types/session-types.js'; import { defaultConnectionInfo, incrementalStateSyncActionType, } from 'lib/types/socket-types.js'; import type { ThreadStoreOperation } from 'lib/types/thread-types.js'; import { updateTypes } from 'lib/types/update-types.js'; import { reduxLoggerMiddleware } from 'lib/utils/action-logger.js'; import { setNewSessionActionType } from 'lib/utils/action-utils.js'; import { convertMessageStoreOperationsToClientDBOperations } from 'lib/utils/message-ops-utils.js'; import { convertThreadStoreOperationsToClientDBOperations } from 'lib/utils/thread-ops-utils.js'; import { resetUserStateActionType, recordNotifPermissionAlertActionType, updateDimensionsActiveType, updateConnectivityActiveType, updateThemeInfoActionType, updateDeviceCameraInfoActionType, updateDeviceOrientationActionType, updateThreadLastNavigatedActionType, backgroundActionTypes, setReduxStateActionType, setStoreLoadedActionType, type Action, } from './action-types.js'; import { remoteReduxDevServerConfig } from './dev-tools.js'; import { defaultDimensionsInfo } from './dimensions-updater.react.js'; import { persistConfig, setPersistor } from './persist.js'; import type { AppState } from './state-types.js'; import { commCoreModule } from '../native-modules.js'; import { defaultNavInfo } from '../navigation/default-state.js'; import { getGlobalNavContext } from '../navigation/icky-global.js'; import { activeMessageListSelector } from '../navigation/nav-selectors.js'; import { defaultNotifPermissionAlertInfo } from '../push/alerts.js'; import reactotron from '../reactotron.js'; import { defaultDeviceCameraInfo } from '../types/camera.js'; import { defaultConnectivityInfo } from '../types/connectivity.js'; import { defaultGlobalThemeInfo } from '../types/themes.js'; import { isTaskCancelledError } from '../utils/error-handling.js'; import { isStaffRelease } from '../utils/staff-utils.js'; import { defaultURLPrefix, natNodeServer, setCustomServer, getDevServerHostname, } from '../utils/url-utils.js'; const defaultState = ({ navInfo: defaultNavInfo, currentUserInfo: null, draftStore: { drafts: {} }, entryStore: { entryInfos: {}, daysToEntries: {}, lastUserInteractionCalendar: 0, }, threadStore: { threadInfos: {}, }, userStore: { userInfos: {}, inconsistencyReports: [], }, messageStore: { messages: {}, threads: {}, local: {}, currentAsOf: 0, }, storeLoaded: false, updatesCurrentAsOf: 0, loadingStatuses: {}, calendarFilters: defaultCalendarFilters, cookie: null, deviceToken: null, dataLoaded: false, urlPrefix: defaultURLPrefix, customServer: natNodeServer, notifPermissionAlertInfo: defaultNotifPermissionAlertInfo, connection: defaultConnectionInfo(Platform.OS), watchedThreadIDs: [], lifecycleState: 'active', enabledApps: defaultEnabledApps, reportStore: { enabledReports: { crashReports: __DEV__, inconsistencyReports: __DEV__, mediaReports: __DEV__, }, queuedReports: [], }, nextLocalID: 0, _persist: null, dimensions: defaultDimensionsInfo, connectivity: defaultConnectivityInfo, globalThemeInfo: defaultGlobalThemeInfo, deviceCameraInfo: defaultDeviceCameraInfo, deviceOrientation: Orientation.getInitialOrientation(), frozen: false, userPolicies: {}, }: AppState); function reducer(state: AppState = defaultState, action: Action) { if (action.type === setReduxStateActionType) { return action.payload.state; } // We want to alert staff/developers if there's a difference between the keys // we expect to see REHYDRATED and the keys that are actually REHYDRATED. // Context: https://linear.app/comm/issue/ENG-2127/ if ( action.type === rehydrateActionType && (__DEV__ || isStaffRelease || (state.currentUserInfo && state.currentUserInfo.id && isStaff(state.currentUserInfo.id))) ) { // 1. Construct set of keys expected to be REHYDRATED const defaultKeys = Object.keys(defaultState); const expectedKeys = defaultKeys.filter( each => !persistConfig.blacklist.includes(each), ); const expectedKeysSet = new Set(expectedKeys); // 2. Construct set of keys actually REHYDRATED const rehydratedKeys = Object.keys(action.payload ?? {}); const rehydratedKeysSet = new Set(rehydratedKeys); // 3. Determine the difference between the two sets const expectedKeysNotRehydrated = expectedKeys.filter( each => !rehydratedKeysSet.has(each), ); const rehydratedKeysNotExpected = rehydratedKeys.filter( each => !expectedKeysSet.has(each), ); // 4. Display alerts with the differences between the two sets if (expectedKeysNotRehydrated.length > 0) { Alert.alert( `EXPECTED KEYS NOT REHYDRATED: ${JSON.stringify( expectedKeysNotRehydrated, )}`, ); } if (rehydratedKeysNotExpected.length > 0) { Alert.alert( `REHYDRATED KEYS NOT EXPECTED: ${JSON.stringify( rehydratedKeysNotExpected, )}`, ); } } if ( (action.type === setNewSessionActionType && invalidSessionDowngrade( state, action.payload.sessionChange.currentUserInfo, action.payload.preRequestUserState, )) || (action.type === logOutActionTypes.success && invalidSessionDowngrade( state, action.payload.currentUserInfo, action.payload.preRequestUserState, )) || (action.type === deleteAccountActionTypes.success && invalidSessionDowngrade( state, action.payload.currentUserInfo, action.payload.preRequestUserState, )) ) { return state; } if ( (action.type === setNewSessionActionType && action.payload.sessionChange.currentUserInfo && invalidSessionRecovery( state, action.payload.sessionChange.currentUserInfo, action.payload.logInActionSource, )) || ((action.type === logInActionTypes.success || action.type === siweAuthActionTypes.success) && invalidSessionRecovery( state, action.payload.currentUserInfo, action.payload.logInActionSource, )) ) { return state; } if (action.type === setCustomServer) { return { ...state, customServer: action.payload, }; } else if (action.type === recordNotifPermissionAlertActionType) { return { ...state, notifPermissionAlertInfo: { totalAlerts: state.notifPermissionAlertInfo.totalAlerts + 1, lastAlertTime: action.payload.time, }, }; } else if (action.type === resetUserStateActionType) { const cookie = state.cookie && state.cookie.startsWith('anonymous=') ? state.cookie : null; const currentUserInfo = state.currentUserInfo && state.currentUserInfo.anonymous ? state.currentUserInfo : null; return { ...state, currentUserInfo, cookie, }; } else if (action.type === updateDimensionsActiveType) { return { ...state, dimensions: { ...state.dimensions, ...action.payload, }, }; } else if (action.type === updateConnectivityActiveType) { return { ...state, connectivity: action.payload, }; } else if (action.type === updateThemeInfoActionType) { return { ...state, globalThemeInfo: { ...state.globalThemeInfo, ...action.payload, }, }; } else if (action.type === updateDeviceCameraInfoActionType) { return { ...state, deviceCameraInfo: { ...state.deviceCameraInfo, ...action.payload, }, }; } else if (action.type === updateDeviceOrientationActionType) { return { ...state, deviceOrientation: action.payload, }; } else if (action.type === setDeviceTokenActionTypes.success) { return { ...state, deviceToken: action.payload, }; } else if (action.type === updateThreadLastNavigatedActionType) { const { threadID, time } = action.payload; if (state.messageStore.threads[threadID]) { state = { ...state, messageStore: { ...state.messageStore, threads: { ...state.messageStore.threads, [threadID]: { ...state.messageStore.threads[threadID], lastNavigatedTo: time, }, }, }, }; } return state; } if (action.type === setNewSessionActionType) { sessionInvalidationAlert(action.payload); state = { ...state, cookie: action.payload.sessionChange.cookie, }; } else if (action.type === incrementalStateSyncActionType) { let wipeDeviceToken = false; for (const update of action.payload.updatesResult.newUpdates) { if ( update.type === updateTypes.BAD_DEVICE_TOKEN && update.deviceToken === state.deviceToken ) { wipeDeviceToken = true; break; } } if (wipeDeviceToken) { state = { ...state, deviceToken: null, }; } } if (action.type === setStoreLoadedActionType) { return { ...state, storeLoaded: true, }; } if (action.type === setClientDBStoreActionType) { state = { ...state, storeLoaded: true, }; const currentLoggedInUserID = state.currentUserInfo?.anonymous ? undefined : state.currentUserInfo?.id; const actionCurrentLoggedInUserID = action.payload.currentUserID; if ( !currentLoggedInUserID || !actionCurrentLoggedInUserID || actionCurrentLoggedInUserID !== currentLoggedInUserID ) { // If user is logged out now, was logged out at the time action was // dispatched or their ID changed between action dispatch and a // call to reducer we ignore the SQLite data since it is not valid return state; } } const baseReducerResult = baseReducer(state, (action: BaseAction)); state = baseReducerResult.state; const { storeOperations } = baseReducerResult; const { draftStoreOperations, threadStoreOperations, messageStoreOperations, } = storeOperations; const fixUnreadActiveThreadResult = fixUnreadActiveThread(state, action); state = fixUnreadActiveThreadResult.state; const threadStoreOperationsWithUnreadFix = [ ...threadStoreOperations, ...fixUnreadActiveThreadResult.threadStoreOperations, ]; const convertedThreadStoreOperations = convertThreadStoreOperationsToClientDBOperations( threadStoreOperationsWithUnreadFix, ); const convertedMessageStoreOperations = convertMessageStoreOperationsToClientDBOperations(messageStoreOperations); (async () => { try { const promises = []; if (convertedThreadStoreOperations.length > 0) { promises.push( commCoreModule.processThreadStoreOperations( convertedThreadStoreOperations, ), ); } if (convertedMessageStoreOperations.length > 0) { promises.push( commCoreModule.processMessageStoreOperations( convertedMessageStoreOperations, ), ); } if (draftStoreOperations.length > 0) { promises.push( commCoreModule.processDraftStoreOperations(draftStoreOperations), ); } 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(); } })(); return state; } function sessionInvalidationAlert(payload: SetSessionPayload) { if ( !payload.sessionChange.cookieInvalidated || !payload.preRequestUserState || !payload.preRequestUserState.currentUserInfo || payload.preRequestUserState.currentUserInfo.anonymous ) { return; } if (payload.error === 'client_version_unsupported') { const app = Platform.select({ ios: 'App Store', android: 'Play Store', }); Alert.alert( 'App out of date', 'Your app version is pretty old, and the server doesn’t know how to ' + `speak to it anymore. Please use the ${app} app to update!`, [{ text: 'OK' }], { cancelable: true }, ); } else { Alert.alert( 'Session invalidated', 'We’re sorry, but your session was invalidated by the server. ' + 'Please log in again.', [{ text: 'OK' }], { cancelable: true }, ); } } // Makes sure a currently focused thread is never unread. Note that we consider // a backgrounded NativeAppState to actually be active if it last changed to // inactive more than 10 seconds ago. This is because there is a delay when // NativeAppState is updating in response to a foreground, and actions don't get // processed more than 10 seconds after a backgrounding anyways. However we // don't consider this for action types that can be expected to happen while the // app is backgrounded. type FixUnreadActiveThreadResult = { +state: AppState, +threadStoreOperations: $ReadOnlyArray, }; function fixUnreadActiveThread( state: AppState, action: *, ): FixUnreadActiveThreadResult { const navContext = getGlobalNavContext(); const activeThread = activeMessageListSelector(navContext); if ( !activeThread || !state.threadStore.threadInfos[activeThread]?.currentUser.unread || (NativeAppState.currentState !== 'active' && (appLastBecameInactive + 10000 >= Date.now() || backgroundActionTypes.has(action.type))) ) { return { state, threadStoreOperations: [] }; } const updatedActiveThreadInfo = { ...state.threadStore.threadInfos[activeThread], currentUser: { ...state.threadStore.threadInfos[activeThread].currentUser, unread: false, }, }; const threadStoreOperations = [ { type: 'replace', payload: { id: activeThread, threadInfo: updatedActiveThreadInfo, }, }, ]; const updatedThreadStore = processThreadStoreOperations( state.threadStore, threadStoreOperations, ); return { state: { ...state, threadStore: updatedThreadStore }, threadStoreOperations, }; } let appLastBecameInactive = 0; function appBecameInactive() { appLastBecameInactive = Date.now(); } const middleware = applyMiddleware(thunk, reduxLoggerMiddleware); let composeFunc = compose; if (__DEV__ && global.HermesInternal) { const { composeWithDevTools } = require('remote-redux-devtools/src/index.js'); composeFunc = composeWithDevTools({ name: 'Redux', hostname: getDevServerHostname(), ...remoteReduxDevServerConfig, }); } else if (global.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) { composeFunc = global.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ name: 'Redux', }); } let enhancers; if (reactotron) { enhancers = composeFunc(middleware, reactotron.createEnhancer()); } else { enhancers = composeFunc(middleware); } const store: Store = createStore( persistReducer(persistConfig, reducer), defaultState, enhancers, ); const persistor = persistStore(store); setPersistor(persistor); const unsafeDispatch: any = store.dispatch; const dispatch: Dispatch = unsafeDispatch; export { store, dispatch, appBecameInactive }; diff --git a/native/schema/CommCoreModuleSchema.js b/native/schema/CommCoreModuleSchema.js index cd03f5ba7..149be4fec 100644 --- a/native/schema/CommCoreModuleSchema.js +++ b/native/schema/CommCoreModuleSchema.js @@ -1,72 +1,73 @@ // @flow 'use strict'; import { TurboModuleRegistry } from 'react-native'; import type { TurboModule } from 'react-native/Libraries/TurboModule/RCTExport.js'; import type { ClientDBDraftInfo, ClientDBDraftStoreOperation, } from 'lib/types/draft-types.js'; import type { ClientDBMessageInfo, ClientDBMessageStoreOperation, } from 'lib/types/message-types.js'; import type { ClientDBThreadInfo, ClientDBThreadStoreOperation, } from 'lib/types/thread-types.js'; type ClientDBStore = { +messages: $ReadOnlyArray, +drafts: $ReadOnlyArray, +threads: $ReadOnlyArray, }; type ClientPublicKeys = { +curve25519: string, +ed25519: string, }; export interface Spec extends TurboModule { +getDraft: (key: string) => Promise; +updateDraft: (key: string, text: string) => Promise; +moveDraft: (oldKey: string, newKey: string) => Promise; +getClientDBStore: () => Promise; +removeAllDrafts: () => Promise; +getAllMessagesSync: () => $ReadOnlyArray; +processDraftStoreOperations: ( operations: $ReadOnlyArray, ) => Promise; +processMessageStoreOperations: ( operations: $ReadOnlyArray, ) => Promise; +processMessageStoreOperationsSync: ( operations: $ReadOnlyArray, ) => void; +getAllThreadsSync: () => $ReadOnlyArray; +processThreadStoreOperations: ( operations: $ReadOnlyArray, ) => Promise; +processThreadStoreOperationsSync: ( operations: $ReadOnlyArray, ) => void; +initializeCryptoAccount: (userId: string) => Promise; +getUserPublicKey: () => Promise; +getUserOneTimeKeys: () => Promise; +getCodeVersion: () => number; +terminate: () => void; +setNotifyToken: (token: string) => Promise; +clearNotifyToken: () => Promise; +setCurrentUserID: (userID: string) => Promise; +getCurrentUserID: () => Promise; +setDeviceID: (deviceType: string) => Promise; +getDeviceID: () => Promise; +clearSensitiveData: () => Promise; +checkIfDatabaseNeedsDeletion: () => boolean; + +reportDBOperationsFailure: () => void; } export default (TurboModuleRegistry.getEnforcing( 'CommTurboModule', ): Spec);