diff --git a/native/cpp/CommonCpp/DatabaseManagers/DatabaseQueryExecutor.h b/native/cpp/CommonCpp/DatabaseManagers/DatabaseQueryExecutor.h index 3847c8e65..55f3e8adb 100644 --- a/native/cpp/CommonCpp/DatabaseManagers/DatabaseQueryExecutor.h +++ b/native/cpp/CommonCpp/DatabaseManagers/DatabaseQueryExecutor.h @@ -1,61 +1,62 @@ #pragma once #include "../CryptoTools/Persist.h" #include "entities/Draft.h" #include "entities/Media.h" #include "entities/Message.h" #include "entities/OlmPersistAccount.h" #include "entities/OlmPersistSession.h" #include "entities/Thread.h" #include #include #include namespace comm { namespace jsi = facebook::jsi; /** * if any initialization/cleaning up steps are required for specific * database managers they should appear in constructors/destructors * following the RAII pattern */ class DatabaseQueryExecutor { public: virtual std::string getDraft(std::string key) const = 0; virtual void updateDraft(std::string key, std::string text) const = 0; virtual bool moveDraft(std::string oldKey, std::string newKey) const = 0; virtual std::vector getAllDrafts() const = 0; virtual void removeAllDrafts() const = 0; virtual void removeAllMessages() const = 0; virtual std::vector>> getAllMessages() const = 0; virtual void removeMessages(const std::vector &ids) const = 0; virtual void removeMessagesForThreads(const std::vector &threadIDs) const = 0; virtual void replaceMessage(const Message &message) const = 0; virtual void rekeyMessage(std::string from, std::string to) const = 0; virtual void removeAllMedia() const = 0; virtual void removeMediaForMessages(const std::vector &msg_ids) const = 0; virtual void removeMediaForMessage(std::string msg_id) const = 0; virtual void removeMediaForThreads(const std::vector &thread_ids) const = 0; virtual void replaceMedia(const Media &media) const = 0; virtual void rekeyMediaContainers(std::string from, std::string to) const = 0; virtual std::vector getAllThreads() const = 0; virtual void removeThreads(std::vector ids) const = 0; virtual void replaceThread(const Thread &thread) const = 0; virtual void removeAllThreads() const = 0; virtual void beginTransaction() const = 0; virtual void commitTransaction() const = 0; virtual void rollbackTransaction() const = 0; virtual std::vector getOlmPersistSessionsData() const = 0; virtual folly::Optional getOlmPersistAccountData() const = 0; virtual void storeOlmPersistData(crypto::Persist persist) const = 0; virtual void setNotifyToken(std::string token) const = 0; + virtual void clearNotifyToken() const = 0; }; } // namespace comm diff --git a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp index b935849cd..49372c26c 100644 --- a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp +++ b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp @@ -1,744 +1,748 @@ #include "SQLiteQueryExecutor.h" #include "Logger.h" #include "sqlite_orm.h" #include "entities/Media.h" #include "entities/Metadata.h" #include #include #include #include #include #include #include #include #include #include #define ACCOUNT_ID 1 namespace comm { using namespace sqlite_orm; std::string SQLiteQueryExecutor::sqliteFilePath; std::string SQLiteQueryExecutor::encryptionKey; bool create_table(sqlite3 *db, std::string query, std::string tableName) { char *error; sqlite3_exec(db, query.c_str(), nullptr, nullptr, &error); if (!error) { return true; } std::ostringstream stringStream; stringStream << "Error creating '" << tableName << "' table: " << error; Logger::log(stringStream.str()); sqlite3_free(error); return false; } bool create_drafts_table(sqlite3 *db) { std::string query = "CREATE TABLE IF NOT EXISTS drafts (threadID TEXT UNIQUE PRIMARY KEY, " "text TEXT);"; return create_table(db, query, "drafts"); } bool rename_threadID_to_key(sqlite3 *db) { sqlite3_stmt *key_column_stmt; sqlite3_prepare_v2( db, "SELECT name AS col_name FROM pragma_table_xinfo ('drafts') WHERE " "col_name='key';", -1, &key_column_stmt, nullptr); sqlite3_step(key_column_stmt); auto num_bytes = sqlite3_column_bytes(key_column_stmt, 0); sqlite3_finalize(key_column_stmt); if (num_bytes) { return true; } char *error; sqlite3_exec( db, "ALTER TABLE drafts RENAME COLUMN `threadID` TO `key`;", nullptr, nullptr, &error); if (error) { std::ostringstream stringStream; stringStream << "Error occurred renaming threadID column in drafts table " << "to key: " << error; Logger::log(stringStream.str()); sqlite3_free(error); return false; } return true; } bool create_persist_account_table(sqlite3 *db) { std::string query = "CREATE TABLE olm_persist_account(" "id INTEGER UNIQUE PRIMARY KEY NOT NULL, " "account_data TEXT NOT NULL);"; return create_table(db, query, "olm_persist_account"); } bool create_persist_sessions_table(sqlite3 *db) { std::string query = "CREATE TABLE olm_persist_sessions(" "target_user_id TEXT UNIQUE PRIMARY KEY NOT NULL, " "session_data TEXT NOT NULL);"; return create_table(db, query, "olm_persist_sessions"); } bool drop_messages_table(sqlite3 *db) { char *error; sqlite3_exec(db, "DROP TABLE IF EXISTS messages;", nullptr, nullptr, &error); if (!error) { return true; } std::ostringstream stringStream; stringStream << "Error dropping 'messages' table: " << error; Logger::log(stringStream.str()); sqlite3_free(error); return false; } bool recreate_messages_table(sqlite3 *db) { std::string query = "CREATE TABLE IF NOT EXISTS messages ( " "id TEXT UNIQUE PRIMARY KEY NOT NULL, " "local_id TEXT, " "thread TEXT NOT NULL, " "user TEXT NOT NULL, " "type INTEGER NOT NULL, " "future_type INTEGER, " "content TEXT, " "time INTEGER NOT NULL);"; return create_table(db, query, "messages"); } bool create_messages_idx_thread_time(sqlite3 *db) { char *error; sqlite3_exec( db, "CREATE INDEX messages_idx_thread_time " "ON messages (thread, time);", nullptr, nullptr, &error); if (!error) { return true; } std::ostringstream stringStream; stringStream << "Error creating (thread, time) index on messages table: " << error; Logger::log(stringStream.str()); sqlite3_free(error); return false; } bool create_media_table(sqlite3 *db) { std::string query = "CREATE TABLE IF NOT EXISTS media ( " "id TEXT UNIQUE PRIMARY KEY NOT NULL, " "container TEXT NOT NULL, " "thread TEXT NOT NULL, " "uri TEXT NOT NULL, " "type TEXT NOT NULL, " "extras TEXT NOT NULL);"; return create_table(db, query, "media"); } bool create_media_idx_container(sqlite3 *db) { char *error; sqlite3_exec( db, "CREATE INDEX media_idx_container " "ON media (container);", nullptr, nullptr, &error); if (!error) { return true; } std::ostringstream stringStream; stringStream << "Error creating (container) index on media table: " << error; Logger::log(stringStream.str()); sqlite3_free(error); return false; } bool create_threads_table(sqlite3 *db) { std::string query = "CREATE TABLE IF NOT EXISTS threads ( " "id TEXT UNIQUE PRIMARY KEY NOT NULL, " "type INTEGER NOT NULL, " "name TEXT, " "description TEXT, " "color TEXT NOT NULL, " "creation_time BIGINT NOT NULL, " "parent_thread_id TEXT, " "containing_thread_id TEXT, " "community TEXT, " "members TEXT NOT NULL, " "roles TEXT NOT NULL, " "current_user TEXT NOT NULL, " "source_message_id TEXT, " "replies_count INTEGER NOT NULL);"; return create_table(db, query, "threads"); } bool update_threadID_for_pending_threads_in_drafts(sqlite3 *db) { char *error; sqlite3_exec( db, "UPDATE drafts SET key = " "REPLACE(REPLACE(REPLACE(REPLACE(key, 'type4/', '')," "'type5/', ''),'type6/', ''),'type7/', '')" "WHERE key LIKE 'pending/%'", nullptr, nullptr, &error); if (!error) { return true; } std::ostringstream stringStream; stringStream << "Error update pending threadIDs on drafts table: " << error; Logger::log(stringStream.str()); sqlite3_free(error); return false; } bool enable_write_ahead_logging_mode(sqlite3 *db) { char *error; sqlite3_exec(db, "PRAGMA journal_mode=wal;", nullptr, nullptr, &error); if (!error) { return true; } std::ostringstream stringStream; stringStream << "Error enabling write-ahead logging mode: " << error; Logger::log(stringStream.str()); sqlite3_free(error); return false; } bool create_metadata_table(sqlite3 *db) { std::string query = "CREATE TABLE IF NOT EXISTS metadata ( " "name TEXT UNIQUE PRIMARY KEY NOT NULL, " "data TEXT);"; return create_table(db, query, "metadata"); } void set_encryption_key(sqlite3 *db) { std::string set_encryption_key_query = "PRAGMA key = \"x'" + SQLiteQueryExecutor::encryptionKey + "'\";"; char *error_set_key; sqlite3_exec( db, set_encryption_key_query.c_str(), nullptr, nullptr, &error_set_key); if (error_set_key) { std::ostringstream error_message; error_message << "Failed to set encryption key: " << error_set_key; throw std::system_error( ECANCELED, std::generic_category(), error_message.str()); } } bool file_exists(const std::string &file_path) { std::ifstream file(file_path.c_str()); return file.good(); } void attempt_delete_file( const std::string &file_path, const char *error_message) { if (std::remove(file_path.c_str())) { throw std::system_error(errno, std::generic_category(), error_message); } } void attempt_rename_file( const std::string &old_path, const std::string &new_path, const char *error_message) { if (std::rename(old_path.c_str(), new_path.c_str())) { throw std::system_error(errno, std::generic_category(), error_message); } } void validate_encryption() { std::string temp_encrypted_db_path = SQLiteQueryExecutor::sqliteFilePath + "_temp_encrypted"; bool temp_encrypted_exists = file_exists(temp_encrypted_db_path); bool default_location_exists = file_exists(SQLiteQueryExecutor::sqliteFilePath); if (temp_encrypted_exists && default_location_exists) { Logger::log( "Previous encryption attempt failed. Repeating encryption process from " "the beginning."); attempt_delete_file( temp_encrypted_db_path, "Failed to delete corrupted encrypted database."); } else if (temp_encrypted_exists && !default_location_exists) { Logger::log( "Moving temporary encrypted database to default location failed in " "previous encryption attempt. Repeating rename step."); attempt_rename_file( temp_encrypted_db_path, SQLiteQueryExecutor::sqliteFilePath, "Failed to move encrypted database to default location."); return; } else if (!default_location_exists) { Logger::log( "Database not present yet. It will be created encrypted under default " "path."); return; } sqlite3 *db; sqlite3_open(SQLiteQueryExecutor::sqliteFilePath.c_str(), &db); set_encryption_key(db); char *key_validation_error; // According to SQLCipher documentation running some SELECT is the only way to // check for key validity sqlite3_exec( db, "SELECT COUNT(*) FROM sqlite_master;", nullptr, nullptr, &key_validation_error); sqlite3_close(db); if (!key_validation_error) { Logger::log( "Database exists under default path and it is correctly encrypted."); return; } Logger::log( "Validation of encryption key failed. Attempting encryption process."); sqlite3_open(SQLiteQueryExecutor::sqliteFilePath.c_str(), &db); std::string createEncryptedCopySQL = "ATTACH DATABASE '" + temp_encrypted_db_path + "' AS encrypted_comm " "KEY \"x'" + SQLiteQueryExecutor::encryptionKey + "'\";" "SELECT sqlcipher_export('encrypted_comm');" "DETACH DATABASE encrypted_comm;"; char *encryption_error; sqlite3_exec( db, createEncryptedCopySQL.c_str(), nullptr, nullptr, &encryption_error); if (encryption_error) { throw std::system_error( ECANCELED, std::generic_category(), "Failed to create encrypted copy of the original database."); } sqlite3_close(db); attempt_delete_file( SQLiteQueryExecutor::sqliteFilePath, "Failed to delete unencrypted database."); attempt_rename_file( temp_encrypted_db_path, SQLiteQueryExecutor::sqliteFilePath, "Failed to move encrypted database to default location."); Logger::log("Encryption completed successfully."); } typedef bool ShouldBeInTransaction; typedef std::pair, ShouldBeInTransaction> SQLiteMigration; std::vector> migrations{ {{1, {create_drafts_table, true}}, {2, {rename_threadID_to_key, true}}, {4, {create_persist_account_table, true}}, {5, {create_persist_sessions_table, true}}, {15, {create_media_table, true}}, {16, {drop_messages_table, true}}, {17, {recreate_messages_table, true}}, {18, {create_messages_idx_thread_time, true}}, {19, {create_media_idx_container, true}}, {20, {create_threads_table, true}}, {21, {update_threadID_for_pending_threads_in_drafts, true}}, {22, {enable_write_ahead_logging_mode, false}}, {23, {create_metadata_table, true}}}}; void SQLiteQueryExecutor::migrate() { validate_encryption(); sqlite3 *db; sqlite3_open(SQLiteQueryExecutor::sqliteFilePath.c_str(), &db); set_encryption_key(db); std::stringstream db_path; db_path << "db path: " << SQLiteQueryExecutor::sqliteFilePath.c_str() << std::endl; Logger::log(db_path.str()); sqlite3_stmt *user_version_stmt; sqlite3_prepare_v2( db, "PRAGMA user_version;", -1, &user_version_stmt, nullptr); sqlite3_step(user_version_stmt); int current_user_version = sqlite3_column_int(user_version_stmt, 0); sqlite3_finalize(user_version_stmt); std::stringstream version_msg; version_msg << "db version: " << current_user_version << std::endl; Logger::log(version_msg.str()); for (const auto &[idx, migration] : migrations) { if (idx <= current_user_version) { continue; } const auto &[applyMigration, shouldBeInTransaction] = migration; std::stringstream migration_msg; if (shouldBeInTransaction) { sqlite3_exec(db, "BEGIN TRANSACTION;", nullptr, nullptr, nullptr); } auto rc = applyMigration(db); if (!rc) { migration_msg << "migration " << idx << " failed." << std::endl; Logger::log(migration_msg.str()); break; } std::stringstream update_version; update_version << "PRAGMA user_version=" << idx << ";"; auto update_version_str = update_version.str(); sqlite3_exec(db, update_version_str.c_str(), nullptr, nullptr, nullptr); if (shouldBeInTransaction) { sqlite3_exec(db, "END TRANSACTION;", nullptr, nullptr, nullptr); } migration_msg << "migration " << idx << " succeeded." << std::endl; Logger::log(migration_msg.str()); } sqlite3_close(db); } auto &SQLiteQueryExecutor::getStorage() { static auto storage = make_storage( SQLiteQueryExecutor::sqliteFilePath, make_table( "drafts", make_column("key", &Draft::key, unique(), primary_key()), make_column("text", &Draft::text)), make_table( "messages", make_column("id", &Message::id, unique(), primary_key()), make_column("local_id", &Message::local_id), make_column("thread", &Message::thread), make_column("user", &Message::user), make_column("type", &Message::type), make_column("future_type", &Message::future_type), make_column("content", &Message::content), make_column("time", &Message::time)), make_table( "olm_persist_account", make_column("id", &OlmPersistAccount::id), make_column("account_data", &OlmPersistAccount::account_data)), make_table( "olm_persist_sessions", make_column("target_user_id", &OlmPersistSession::target_user_id), make_column("session_data", &OlmPersistSession::session_data)), make_table( "media", make_column("id", &Media::id, unique(), primary_key()), make_column("container", &Media::container), make_column("thread", &Media::thread), make_column("uri", &Media::uri), make_column("type", &Media::type), make_column("extras", &Media::extras)), make_table( "threads", make_column("id", &Thread::id, unique(), primary_key()), make_column("type", &Thread::type), make_column("name", &Thread::name), make_column("description", &Thread::description), make_column("color", &Thread::color), make_column("creation_time", &Thread::creation_time), make_column("parent_thread_id", &Thread::parent_thread_id), make_column("containing_thread_id", &Thread::containing_thread_id), make_column("community", &Thread::community), make_column("members", &Thread::members), make_column("roles", &Thread::roles), make_column("current_user", &Thread::current_user), make_column("source_message_id", &Thread::source_message_id), make_column("replies_count", &Thread::replies_count)), make_table( "metadata", make_column("name", &Metadata::name, unique(), primary_key()), make_column("data", &Metadata::data))); storage.on_open = set_encryption_key; return storage; } SQLiteQueryExecutor::SQLiteQueryExecutor() { this->migrate(); } std::string SQLiteQueryExecutor::getDraft(std::string key) const { std::unique_ptr draft = SQLiteQueryExecutor::getStorage().get_pointer(key); return (draft == nullptr) ? "" : draft->text; } void SQLiteQueryExecutor::updateDraft(std::string key, std::string text) const { Draft draft = {key, text}; SQLiteQueryExecutor::getStorage().replace(draft); } bool SQLiteQueryExecutor::moveDraft(std::string oldKey, std::string newKey) const { std::unique_ptr draft = SQLiteQueryExecutor::getStorage().get_pointer(oldKey); if (draft == nullptr) { return false; } draft->key = newKey; SQLiteQueryExecutor::getStorage().replace(*draft); SQLiteQueryExecutor::getStorage().remove(oldKey); return true; } std::vector SQLiteQueryExecutor::getAllDrafts() const { return SQLiteQueryExecutor::getStorage().get_all(); } void SQLiteQueryExecutor::removeAllDrafts() const { SQLiteQueryExecutor::getStorage().remove_all(); } void SQLiteQueryExecutor::removeAllMessages() const { SQLiteQueryExecutor::getStorage().remove_all(); } std::vector>> SQLiteQueryExecutor::getAllMessages() const { auto rows = SQLiteQueryExecutor::getStorage().select( columns( &Message::id, &Message::local_id, &Message::thread, &Message::user, &Message::type, &Message::future_type, &Message::content, &Message::time, &Media::id, &Media::container, &Media::thread, &Media::uri, &Media::type, &Media::extras), left_join(on(c(&Message::id) == &Media::container)), order_by(&Message::id)); std::vector>> allMessages; allMessages.reserve(rows.size()); std::string prev_msg_idx{}; for (auto &row : rows) { auto msg_id = std::get<0>(row); if (msg_id == prev_msg_idx) { allMessages.back().second.push_back(Media{ std::get<8>(row), std::move(std::get<9>(row)), std::move(std::get<10>(row)), std::move(std::get<11>(row)), std::move(std::get<12>(row)), std::move(std::get<13>(row)), }); } else { std::vector mediaForMsg; if (!std::get<8>(row).empty()) { mediaForMsg.push_back(Media{ std::get<8>(row), std::move(std::get<9>(row)), std::move(std::get<10>(row)), std::move(std::get<11>(row)), std::move(std::get<12>(row)), std::move(std::get<13>(row)), }); } allMessages.push_back(std::make_pair( Message{ msg_id, std::move(std::get<1>(row)), std::move(std::get<2>(row)), std::move(std::get<3>(row)), std::get<4>(row), std::move(std::get<5>(row)), std::move(std::get<6>(row)), std::get<7>(row)}, mediaForMsg)); prev_msg_idx = msg_id; } } return allMessages; } void SQLiteQueryExecutor::removeMessages( const std::vector &ids) const { SQLiteQueryExecutor::getStorage().remove_all( where(in(&Message::id, ids))); } void SQLiteQueryExecutor::removeMessagesForThreads( const std::vector &threadIDs) const { SQLiteQueryExecutor::getStorage().remove_all( where(in(&Message::thread, threadIDs))); } void SQLiteQueryExecutor::replaceMessage(const Message &message) const { SQLiteQueryExecutor::getStorage().replace(message); } void SQLiteQueryExecutor::rekeyMessage(std::string from, std::string to) const { auto msg = SQLiteQueryExecutor::getStorage().get(from); msg.id = to; SQLiteQueryExecutor::getStorage().replace(msg); SQLiteQueryExecutor::getStorage().remove(from); } void SQLiteQueryExecutor::removeAllMedia() const { SQLiteQueryExecutor::getStorage().remove_all(); } void SQLiteQueryExecutor::removeMediaForMessages( const std::vector &msg_ids) const { SQLiteQueryExecutor::getStorage().remove_all( where(in(&Media::container, msg_ids))); } void SQLiteQueryExecutor::removeMediaForMessage(std::string msg_id) const { SQLiteQueryExecutor::getStorage().remove_all( where(c(&Media::container) == msg_id)); } void SQLiteQueryExecutor::removeMediaForThreads( const std::vector &thread_ids) const { SQLiteQueryExecutor::getStorage().remove_all( where(in(&Media::thread, thread_ids))); } void SQLiteQueryExecutor::replaceMedia(const Media &media) const { SQLiteQueryExecutor::getStorage().replace(media); } void SQLiteQueryExecutor::rekeyMediaContainers(std::string from, std::string to) const { SQLiteQueryExecutor::getStorage().update_all( set(c(&Media::container) = to), where(c(&Media::container) == from)); } std::vector SQLiteQueryExecutor::getAllThreads() const { return SQLiteQueryExecutor::getStorage().get_all(); }; void SQLiteQueryExecutor::removeThreads(std::vector ids) const { SQLiteQueryExecutor::getStorage().remove_all( where(in(&Thread::id, ids))); }; void SQLiteQueryExecutor::replaceThread(const Thread &thread) const { SQLiteQueryExecutor::getStorage().replace(thread); }; void SQLiteQueryExecutor::removeAllThreads() const { SQLiteQueryExecutor::getStorage().remove_all(); }; void SQLiteQueryExecutor::beginTransaction() const { SQLiteQueryExecutor::getStorage().begin_transaction(); } void SQLiteQueryExecutor::commitTransaction() const { SQLiteQueryExecutor::getStorage().commit(); } void SQLiteQueryExecutor::rollbackTransaction() const { SQLiteQueryExecutor::getStorage().rollback(); } std::vector SQLiteQueryExecutor::getOlmPersistSessionsData() const { return SQLiteQueryExecutor::getStorage().get_all(); } folly::Optional SQLiteQueryExecutor::getOlmPersistAccountData() const { std::vector result = SQLiteQueryExecutor::getStorage().get_all(); if (result.size() > 1) { throw std::system_error( ECANCELED, std::generic_category(), "Multiple records found for the olm_persist_account table"); } return (result.size() == 0) ? folly::none : folly::Optional(result[0].account_data); } void SQLiteQueryExecutor::storeOlmPersistData(crypto::Persist persist) const { OlmPersistAccount persistAccount = { ACCOUNT_ID, std::string(persist.account.begin(), persist.account.end())}; SQLiteQueryExecutor::getStorage().replace(persistAccount); for (auto it = persist.sessions.begin(); it != persist.sessions.end(); it++) { OlmPersistSession persistSession = { it->first, std::string(it->second.begin(), it->second.end())}; SQLiteQueryExecutor::getStorage().replace(persistSession); } } void SQLiteQueryExecutor::setNotifyToken(std::string token) const { Metadata entry{ "notify_token", token, }; SQLiteQueryExecutor::getStorage().replace(entry); } +void SQLiteQueryExecutor::clearNotifyToken() const { + SQLiteQueryExecutor::getStorage().remove("notify_token"); +} + } // namespace comm diff --git a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h index 639b1940e..90804deed 100644 --- a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h +++ b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h @@ -1,54 +1,55 @@ #pragma once #include "../CryptoTools/Persist.h" #include "DatabaseQueryExecutor.h" #include "entities/Draft.h" #include namespace comm { class SQLiteQueryExecutor : public DatabaseQueryExecutor { void migrate(); static auto &getStorage(); public: static std::string sqliteFilePath; static std::string encryptionKey; SQLiteQueryExecutor(); std::string getDraft(std::string key) const override; void updateDraft(std::string key, std::string text) const override; bool moveDraft(std::string oldKey, std::string newKey) const override; std::vector getAllDrafts() const override; void removeAllDrafts() const override; void removeAllMessages() const override; std::vector>> getAllMessages() const override; void removeMessages(const std::vector &ids) const override; void removeMessagesForThreads( const std::vector &threadIDs) const override; void replaceMessage(const Message &message) const override; void rekeyMessage(std::string from, std::string to) const override; void removeAllMedia() const override; void removeMediaForMessages( const std::vector &msg_ids) const override; void removeMediaForMessage(std::string msg_id) const override; void removeMediaForThreads( const std::vector &thread_ids) const override; void replaceMedia(const Media &media) const override; void rekeyMediaContainers(std::string from, std::string to) const override; std::vector getAllThreads() const override; void removeThreads(std::vector ids) const override; void replaceThread(const Thread &thread) const override; void removeAllThreads() const override; void beginTransaction() const override; void commitTransaction() const override; void rollbackTransaction() const override; std::vector getOlmPersistSessionsData() const override; folly::Optional getOlmPersistAccountData() const override; void storeOlmPersistData(crypto::Persist persist) const override; void setNotifyToken(std::string token) const override; + void clearNotifyToken() const override; }; } // namespace comm diff --git a/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp b/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp index 145f20782..47a3b044e 100644 --- a/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp +++ b/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp @@ -1,935 +1,956 @@ #include "CommCoreModule.h" #include "DatabaseManager.h" #include "GRPCStreamHostObject.h" #include "InternalModules/GlobalNetworkSingleton.h" #include "InternalModules/NetworkModule.h" #include "Logger.h" #include "MessageStoreOperations.h" #include "ThreadStoreOperations.h" #include #include "../DatabaseManagers/entities/Media.h" #include #include namespace comm { using namespace facebook::react; 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)); }); }; this->databaseThread->scheduleTask(job); }); } 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); 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); } }); }; this->databaseThread->scheduleTask(job); }); } 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); } }); }; this->databaseThread->scheduleTask(job); }); } 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)); }); }; this->databaseThread->scheduleTask(job); }); } 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()); }); }; this->databaseThread->scheduleTask(job); }); } jsi::Array CommCoreModule::getAllMessagesSync(jsi::Runtime &rt) { std::promise>>> messagesResult; auto messagesResultFuture = messagesResult.get_future(); this->databaseThread->scheduleTask([&messagesResult]() { messagesResult.set_value( DatabaseManager::getQueryExecutor().getAllMessages()); }); auto messagesVector = messagesResultFuture.get(); 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)); }); }; this->databaseThread->scheduleTask(job); }); } #define REKEY_OPERATION "rekey" #define REMOVE_OPERATION "remove" #define REPLACE_OPERATION "replace" #define REMOVE_MSGS_FOR_THREADS_OPERATION "remove_messages_for_threads" #define 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); 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 if (op_type == REMOVE_ALL_OPERATION) { messageStoreOps.push_back(std::make_unique()); } 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()); } }); }; this->databaseThread->scheduleTask(job); }); } bool CommCoreModule::processMessageStoreOperationsSync( jsi::Runtime &rt, const jsi::Array &operations) { std::promise operationsResult; std::future operationsResultFuture = operationsResult.get_future(); std::vector> messageStoreOps; std::string operationsError; try { messageStoreOps = createMessageStoreOperations(rt, operations); } catch (std::runtime_error &e) { return false; } this->databaseThread->scheduleTask( [=, &messageStoreOps, &operationsResult, &rt]() { std::string error = operationsError; if (!error.size()) { try { DatabaseManager::getQueryExecutor().beginTransaction(); for (const auto &operation : messageStoreOps) { operation->execute(); } DatabaseManager::getQueryExecutor().commitTransaction(); } catch (std::system_error &e) { error = e.what(); DatabaseManager::getQueryExecutor().rollbackTransaction(); } } operationsResult.set_value(error.size() == 0); }); return operationsResultFuture.get(); } jsi::Value CommCoreModule::getAllThreads(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { this->databaseThread->scheduleTask([=, &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)); }); }); }); }; jsi::Array CommCoreModule::getAllThreadsSync(jsi::Runtime &rt) { std::promise> threadsResult; auto threadsResultFuture = threadsResult.get_future(); this->databaseThread->scheduleTask([&threadsResult]() { threadsResult.set_value( DatabaseManager::getQueryExecutor().getAllThreads()); }); auto threadsVector = threadsResultFuture.get(); 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) { this->databaseThread->scheduleTask([=]() { 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()); } }); }); }); } bool CommCoreModule::processThreadStoreOperationsSync( jsi::Runtime &rt, const jsi::Array &operations) { std::promise operationsResult; std::future operationsResultFuture = operationsResult.get_future(); std::vector> threadStoreOps; std::string operationsError; try { threadStoreOps = createThreadStoreOperations(rt, operations); } catch (std::runtime_error &e) { return false; } this->databaseThread->scheduleTask( [=, &threadStoreOps, &operationsResult, &rt]() { std::string error = operationsError; if (!error.size()) { try { DatabaseManager::getQueryExecutor().beginTransaction(); for (const auto &operation : threadStoreOps) { operation->execute(); } DatabaseManager::getQueryExecutor().commitTransaction(); } catch (std::system_error &e) { error = e.what(); DatabaseManager::getQueryExecutor().rollbackTransaction(); } } operationsResult.set_value(error.size() == 0); }); return operationsResultFuture.get(); } 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) { this->databaseThread->scheduleTask([=]() { 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()); this->databaseThread->scheduleTask([=]() { 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()); }); }); } else { this->cryptoModule->restoreFromB64( storedSecretKey.value(), persist); this->jsInvoker_->invokeAsync([=]() { if (error.size()) { promise->reject(error); return; } promise->resolve(jsi::Value::undefined()); }); } }); }); }); } void CommCoreModule::initializeNetworkModule( const std::string &userId, const std::string &deviceToken, const std::string &hostname) { GlobalNetworkSingleton::instance.scheduleOrRun( [=](NetworkModule &networkModule) { networkModule.initializeNetworkModule(userId, deviceToken, hostname); }); } 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); }); } jsi::Object CommCoreModule::openSocket(jsi::Runtime &rt, const jsi::String &endpoint) { auto hostObject = std::make_shared(rt, this->jsInvoker_); return jsi::Object::createFromHostObject(rt, hostObject); } CommCoreModule::CommCoreModule( std::shared_ptr jsInvoker) : facebook::react::CommCoreModuleSchemaCxxSpecJSI(jsInvoker), databaseThread(std::make_unique("database")), cryptoThread(std::make_unique("crypto")) { GlobalNetworkSingleton::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) { this->databaseThread->scheduleTask([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()); + } + }); + }); + }); +} + +jsi::Value CommCoreModule::clearNotifyToken(jsi::Runtime &rt) { + return createPromiseAsJSIValue( + rt, [this](jsi::Runtime &innerRt, std::shared_ptr promise) { + this->databaseThread->scheduleTask([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()); } }); }); }); }; } // namespace comm diff --git a/native/cpp/CommonCpp/NativeModules/CommCoreModule.h b/native/cpp/CommonCpp/NativeModules/CommCoreModule.h index bd5c59270..0ac3eae06 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/NativeModules.h" #include "../grpc/Client.h" #include #include namespace comm { namespace jsi = facebook::jsi; class CommCoreModule : public facebook::react::CommCoreModuleSchemaCxxSpecJSI { const int codeVersion{129}; std::unique_ptr databaseThread; std::unique_ptr cryptoThread; CommSecureStore secureStore; const std::string secureStoreAccountDataKey = "cryptoAccountDataKey"; std::unique_ptr cryptoModule; std::unique_ptr networkClient; jsi::Value getDraft(jsi::Runtime &rt, const jsi::String &key) override; jsi::Value updateDraft(jsi::Runtime &rt, const jsi::Object &draft) 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 processMessageStoreOperations( jsi::Runtime &rt, const jsi::Array &operations) override; bool 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; bool 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; jsi::Object openSocket(jsi::Runtime &rt, const jsi::String &endpoint) 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; public: CommCoreModule(std::shared_ptr jsInvoker); void initializeNetworkModule( const std::string &userId, const std::string &deviceToken, const std::string &hostname = ""); }; } // namespace comm diff --git a/native/cpp/CommonCpp/_generated/NativeModules.cpp b/native/cpp/CommonCpp/_generated/NativeModules.cpp index 621143258..7e601ba55 100644 --- a/native/cpp/CommonCpp/_generated/NativeModules.cpp +++ b/native/cpp/CommonCpp/_generated/NativeModules.cpp @@ -1,98 +1,102 @@ /** * 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)); } 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_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) { return static_cast(&turboModule)->processMessageStoreOperationsSync(rt, args[0].getObject(rt).getArray(rt)); } 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) { return static_cast(&turboModule)->processThreadStoreOperationsSync(rt, args[0].getObject(rt).getArray(rt)); } 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_openSocket(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->openSocket(rt, args[0].getString(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); +} CommCoreModuleSchemaCxxSpecJSI::CommCoreModuleSchemaCxxSpecJSI(std::shared_ptr jsInvoker) : TurboModule("CommTurboModule", jsInvoker) { methodMap_["getDraft"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getDraft}; methodMap_["updateDraft"] = MethodMetadata {1, __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_["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_["openSocket"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_openSocket}; methodMap_["getCodeVersion"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getCodeVersion}; methodMap_["setNotifyToken"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_setNotifyToken}; + methodMap_["clearNotifyToken"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_clearNotifyToken}; } } // namespace react } // namespace facebook diff --git a/native/cpp/CommonCpp/_generated/NativeModules.h b/native/cpp/CommonCpp/_generated/NativeModules.h index 84c6f7431..c247310a6 100644 --- a/native/cpp/CommonCpp/_generated/NativeModules.h +++ b/native/cpp/CommonCpp/_generated/NativeModules.h @@ -1,44 +1,45 @@ /** * 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 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 processMessageStoreOperations(jsi::Runtime &rt, const jsi::Array &operations) = 0; virtual bool 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 bool 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 jsi::Object openSocket(jsi::Runtime &rt, const jsi::String &endpoint) = 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; }; } // namespace react } // namespace facebook diff --git a/native/schema/CommCoreModuleSchema.js b/native/schema/CommCoreModuleSchema.js index 52cc447cc..99e53c1e6 100644 --- a/native/schema/CommCoreModuleSchema.js +++ b/native/schema/CommCoreModuleSchema.js @@ -1,54 +1,55 @@ // @flow 'use strict'; import { TurboModuleRegistry } from 'react-native'; import type { TurboModule } from 'react-native/Libraries/TurboModule/RCTExport'; import type { ClientDBMessageInfo, ClientDBMessageStoreOperation, } from 'lib/types/message-types'; import type { ClientDBThreadInfo, ClientDBThreadStoreOperation, } from 'lib/types/thread-types'; type ClientDBDraftInfo = { +key: string, +text: string, }; export interface Spec extends TurboModule { +getDraft: (key: string) => Promise; +updateDraft: (draft: ClientDBDraftInfo) => Promise; +moveDraft: (oldKey: string, newKey: string) => Promise; +getAllDrafts: () => Promise<$ReadOnlyArray>; +removeAllDrafts: () => Promise; +getAllMessages: () => Promise<$ReadOnlyArray>; +getAllMessagesSync: () => $ReadOnlyArray; +processMessageStoreOperations: ( operations: $ReadOnlyArray, ) => Promise; +processMessageStoreOperationsSync: ( operations: $ReadOnlyArray, ) => boolean; +getAllThreads: () => Promise<$ReadOnlyArray>; +getAllThreadsSync: () => $ReadOnlyArray; +processThreadStoreOperations: ( operations: $ReadOnlyArray, ) => Promise; +processThreadStoreOperationsSync: ( operations: $ReadOnlyArray, ) => boolean; +initializeCryptoAccount: (userId: string) => Promise; +getUserPublicKey: () => Promise; +getUserOneTimeKeys: () => Promise; +openSocket: (endpoint: string) => Object; +getCodeVersion: () => number; +setNotifyToken: (token: string) => Promise; + +clearNotifyToken: () => Promise; } export default (TurboModuleRegistry.getEnforcing( 'CommTurboModule', ): Spec);