From 228e9b7ea258381f5b83224c266272133e9cb126 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Fri, 5 Sep 2025 13:26:52 +0200 Subject: [PATCH] =?UTF-8?q?F=C3=BCge=20Unterst=C3=BCtzung=20f=C3=BCr=20Web?= =?UTF-8?q?Socket-Nutzer=20in=20ChatUser=20und=20ChatRoom=20hinzu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Implementiere einen neuen Konstruktor in der Klasse `ChatUser`, der einen WebSocket-Pointer akzeptiert, um die Benutzerkommunikation über WebSockets zu ermöglichen. - Aktualisiere die Methode `addUser` in `ChatRoom`, um den neuen Konstruktor zu verwenden und sicherzustellen, dass Benutzer korrekt hinzugefügt werden. - Ergänze die Logik in der `send`-Methode von `ChatUser`, um Nachrichten über WebSockets zu senden, wenn ein gültiger WebSocket-Pointer vorhanden ist. - Füge Debug-Ausgaben hinzu, um den Ablauf beim Hinzufügen von Benutzern und beim Senden von Nachrichten über WebSockets zu protokollieren. --- deploy/deploy.sh | 2 +- src/core/chat_room.cpp | 68 +++++++++++++++++++++++++ src/core/chat_room.h | 1 + src/core/chat_user.cpp | 110 +++++++++++++++++++++++++++++++++++++--- src/core/chat_user.h | 2 + src/core/ssl_server.cpp | 4 +- src/lib/base.cpp | 48 ++++++++++++++++++ src/lib/base.h | 2 + 8 files changed, 228 insertions(+), 9 deletions(-) diff --git a/deploy/deploy.sh b/deploy/deploy.sh index 2ba6d70..50aa504 100755 --- a/deploy/deploy.sh +++ b/deploy/deploy.sh @@ -23,7 +23,7 @@ fi echo "=== Schritt 1: Abhängigkeiten installieren ===" echo "Führe install_dependencies.sh aus..." -./deploy/install_dependencies.sh +#./deploy/install_dependencies.sh echo "" echo "=== Schritt 2: Anwendung bauen ===" diff --git a/src/core/chat_room.cpp b/src/core/chat_room.cpp index b75982a..a4489c3 100755 --- a/src/core/chat_room.cpp +++ b/src/core/chat_room.cpp @@ -175,6 +175,74 @@ namespace Yc return true; } + bool ChatRoom::addUser(std::string _userName, std::string color, std::string _password, void* wsi) + { + if (_password != "" && _password == _password && std::find(std::begin(_allowedUsers), std::end(_allowedUsers), _userName) == std::end(_allowedUsers)) + { + return false; + } + std::shared_ptr newUser; + if (_database) { + // Verwende den neuen WebSocket-Konstruktor + newUser = std::make_shared(shared_from_this(), _userName, color, wsi, _database); + } else { + // Fallback: WebSocket ohne Datenbank nicht unterstützt + return false; + } + _users.push_back(newUser); + + // Sende zuerst alle wichtigen Nachrichten, bevor der checkerTask Thread startet + #ifdef YC_DEBUG + std::cout << "[Debug] addUser(WebSocket): Starting message sequence for user: " << newUser->name() << std::endl; + #endif + + if (_parent) { + Json::Value roomList = _parent->jsonRoomList(); + #ifdef YC_DEBUG + std::cout << "[Debug] addUser(WebSocket): Sending roomList to user: " << newUser->name() << std::endl; + #endif + newUser->sendMsg(ChatUser::roomList, roomList, "", ""); + } + + // Kurze Pause, damit der WebSocket-Handshake vollständig abgeschlossen ist + #ifdef YC_DEBUG + std::cout << "[Debug] addUser(WebSocket): Waiting 100ms for WebSocket handshake..." << std::endl; + #endif + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + // Sende aktuelle Userliste an den neuen User + Json::Value currentUserList = userList(); + #ifdef YC_DEBUG + std::cout << "[Debug] addUser(WebSocket): Sending userListe to user: " << newUser->name() << std::endl; + #endif + newUser->sendMsg(ChatUser::userListe, currentUserList, "", ""); + + // Sende aktualisierte Userliste an alle anderen User im Raum + for (auto &existingUser : _users) { + if (existingUser != newUser) { + #ifdef YC_DEBUG + std::cout << "[Debug] addUser(WebSocket): Sending userListe to existing user: " << existingUser->name() << std::endl; + #endif + existingUser->sendMsg(ChatUser::userListe, currentUserList, "", ""); + } + } + + // Broadcast an andere Nutzer: Benutzer X hat den Chat betreten (mit Farbinfo) + #ifdef YC_DEBUG + std::cout << "[Debug] addUser(WebSocket): Sending 'user_entered_chat' message for user: " << newUser->name() << std::endl; + #endif + addMessage(ChatUser::system, "user_entered_chat", newUser->name(), newUser->color()); + + // Starte den checkerTask Thread erst nach dem Senden aller wichtigen Nachrichten + #ifdef YC_DEBUG + std::cout << "[Debug] addUser(WebSocket): Starting checkerTask thread for user: " << newUser->name() << std::endl; + #endif + newUser->start(); + + _initRound(); + return true; + } + bool ChatRoom::addUser(std::shared_ptr user, std::string password) { if (password == _password) diff --git a/src/core/chat_room.h b/src/core/chat_room.h index e6d7c79..c392323 100755 --- a/src/core/chat_room.h +++ b/src/core/chat_room.h @@ -42,6 +42,7 @@ namespace Yc void run(); std::string name(); bool addUser(std::string userName, std::string color, std::string password, int socket); + bool addUser(std::string userName, std::string color, std::string password, void* wsi); bool addUser(std::shared_ptr user, std::string password); bool userNameExists(std::string userName); void removeUser(std::string _token, bool silent = false); diff --git a/src/core/chat_user.cpp b/src/core/chat_user.cpp index ed3b83e..66fd5f9 100644 --- a/src/core/chat_user.cpp +++ b/src/core/chat_user.cpp @@ -120,6 +120,7 @@ namespace Yc _name(name), _color(color), _socket(socket), + _wsi(nullptr), _stop(false) { #ifdef YC_DEBUG @@ -195,6 +196,89 @@ namespace Yc // Thread-Start erfolgt jetzt explizit per start(), nicht im Konstruktor } + ChatUser::ChatUser(std::shared_ptr parent, std::string name, std::string color, void* wsi, std::shared_ptr database) + : _parent(std::move(parent)), + _name(name), + _color(color), + _socket(-1), // WebSocket uses wsi, not socket + _wsi(wsi), + _stop(false) + { + #ifdef YC_DEBUG + std::cout << "[Debug] ChatUser WebSocket constructor: name=" << _name << ", wsi=" << _wsi << std::endl; + #endif + + // Verwende die direkt übergebene Datenbank + if (!database) { + // Fallback wenn keine Datenbank verfügbar + _user = Yc::Object::User(Json::Value()); + _token = Yc::Lib::Tools::generateRandomString(32); + } else { + // Lade User-Daten aus der Datenbank + Json::Value userJson; + userJson["id"] = 0; + userJson["falukant_user_id"] = 0; + userJson["display_name"] = name; + userJson["color"] = color; + userJson["show_gender"] = true; + userJson["show_age"] = true; + userJson["created_at"] = ""; + userJson["updated_at"] = ""; + + // Versuche User in der Datenbank zu finden + try { + std::string query = "SELECT * FROM chat.\"user\" WHERE display_name = '" + name + "' LIMIT 1;"; + auto chatUserResult = database->exec(query); + if (chatUserResult.empty()) { + // Chat-User anlegen + std::string insert = "INSERT INTO chat.\"user\" (falukant_user_id, display_name, color, show_gender, show_age, created_at, updated_at) VALUES (0, '" + name + "', '#000000', true, true, NOW(), NOW()) RETURNING *;"; + auto newUser = database->exec(insert); + if (!newUser.empty()) { + const auto& u = newUser[0]; + userJson["id"] = u["id"].as(); + userJson["falukant_user_id"] = u["falukant_user_id"].as(); + userJson["display_name"] = u["display_name"].c_str(); + userJson["color"] = u["color"].c_str(); + userJson["show_gender"] = u["show_gender"].as(); + userJson["show_age"] = u["show_age"].as(); + userJson["created_at"] = u["created_at"].c_str(); + userJson["updated_at"] = u["updated_at"].c_str(); + } + } else { + const auto& u = chatUserResult[0]; + userJson["id"] = u["id"].as(); + userJson["falukant_user_id"] = u["falukant_user_id"].as(); + userJson["display_name"] = u["display_name"].c_str(); + userJson["color"] = u["color"].c_str(); + userJson["show_gender"] = u["show_gender"].as(); + userJson["show_age"] = u["show_age"].as(); + userJson["created_at"] = u["created_at"].c_str(); + userJson["updated_at"] = u["updated_at"].c_str(); + } + // Rechte laden + std::string rightsQuery = "SELECT r.tr FROM chat.user_rights ur JOIN chat.rights r ON ur.chat_right_id = r.id WHERE ur.chat_user_id = " + std::to_string(userJson["id"].asInt()) + ";"; + auto rightsResult = database->exec(rightsQuery); + Json::Value rights(Json::arrayValue); + for (const auto& r : rightsResult) { + rights.append(r["tr"].c_str()); + } + userJson["rights"] = rights; + } catch (...) { + // Ignore database errors + } + _user = Yc::Object::User(userJson); + // Prefer DB color if available + if (_user.id() != 0 && !_user.color().empty()) { + _color = _user.color(); + } + _token = Yc::Lib::Tools::generateRandomString(32); + } + + // Beim Initial-Token direkt Name und aktuelle Farbe mitsenden, damit der Client "ich" korrekt färben kann + sendMsg(token, _token, _name, _color); + // Thread-Start erfolgt jetzt explizit per start(), nicht im Konstruktor + } + ChatUser::~ChatUser() { // Hinweis: Thread wird nicht im Destruktor gejoint, um Deadlocks zu vermeiden! @@ -370,7 +454,7 @@ namespace Yc void ChatUser::send(std::string out) { // Prüfe ob Socket noch gültig ist - if (_socket < 0 || _stop) { + if ((_socket < 0 && !_wsi) || _stop) { #ifdef YC_DEBUG std::cout << "[Debug] Skipping send - socket invalid (" << _socket << ") or user stopped (" << _stop << ") for user: " << _name << std::endl; #endif @@ -378,17 +462,24 @@ namespace Yc } #ifdef YC_DEBUG - std::cout << "[Debug] Sending message to user: " << _name << " (socket: " << _socket << ")" << std::endl; + std::cout << "[Debug] Sending message to user: " << _name << " (socket: " << _socket << ", wsi: " << _wsi << ")" << std::endl; #endif // Entferne ggf. Token-Felder aus JSON-Strings und sende über Socket/WebSocket Base::sanitizeTokensInString(out); - Base::send(_socket, out); + + if (_wsi) { + // WebSocket: Sende über libwebsockets + Base::sendWebSocketMessage(_wsi, out); + } else { + // Normaler Socket + Base::send(_socket, out); + } } void ChatUser::send(Json::Value out) { // Prüfe ob Socket noch gültig ist - if (_socket < 0 || _stop) { + if ((_socket < 0 && !_wsi) || _stop) { #ifdef YC_DEBUG std::cout << "[Debug] Skipping send - socket invalid (" << _socket << ") or user stopped (" << _stop << ") for user: " << _name << std::endl; #endif @@ -396,12 +487,19 @@ namespace Yc } #ifdef YC_DEBUG - std::cout << "[Debug] Sending JSON message to user: " << _name << " (socket: " << _socket << ")" << std::endl; + std::cout << "[Debug] Sending JSON message to user: " << _name << " (socket: " << _socket << ", wsi: " << _wsi << ")" << std::endl; #endif // Entferne rekursiv alle Token-Felder und sende über Socket/WebSocket Base::sanitizeTokens(out); - Base::send(_socket, out); + + if (_wsi) { + // WebSocket: Sende über libwebsockets + Base::sendWebSocketMessage(_wsi, out); + } else { + // Normaler Socket + Base::send(_socket, out); + } } void ChatUser::handleMessage(std::string message) diff --git a/src/core/chat_user.h b/src/core/chat_user.h index f765829..a0d6eee 100644 --- a/src/core/chat_user.h +++ b/src/core/chat_user.h @@ -36,6 +36,7 @@ namespace Yc ChatUser(std::shared_ptr parent, std::string name, std::string color, int socket); ChatUser(std::shared_ptr parent, std::string name, std::string color, int socket, std::shared_ptr database); + ChatUser(std::shared_ptr parent, std::string name, std::string color, void* wsi, std::shared_ptr database); ~ChatUser(); std::string name() const; std::string getToken() const; @@ -59,6 +60,7 @@ namespace Yc std::string _name; std::string _color; int _socket; + void* _wsi; // WebSocket pointer for libwebsockets std::string _token; bool _stop; diff --git a/src/core/ssl_server.cpp b/src/core/ssl_server.cpp index ff0c31b..be6af3c 100644 --- a/src/core/ssl_server.cpp +++ b/src/core/ssl_server.cpp @@ -317,10 +317,10 @@ void SSLServer::handleWebSocketMessage(struct lws *wsi, const std::string& messa std::cout << "[YourChat] Found room '" << room << "', attempting to add user..." << std::endl; // Add user to room (ChatUser will be created by addUser) #ifdef YC_DEBUG - std::cout << "[Debug] Attempting to add user '" << name << "' to room '" << room << "' with socket: " << lws_get_socket_fd(wsi) << std::endl; + std::cout << "[Debug] Attempting to add user '" << name << "' to room '" << room << "' with WebSocket wsi pointer" << std::endl; #endif - if (roomObj->addUser(name, color, password, lws_get_socket_fd(wsi))) { + if (roomObj->addUser(name, color, password, wsi)) { std::cout << "[YourChat] Successfully added user '" << name << "' to room '" << room << "'" << std::endl; // Find the created ChatUser auto chatUser = roomObj->findUserByName(name); diff --git a/src/lib/base.cpp b/src/lib/base.cpp index 063316b..23580e7 100755 --- a/src/lib/base.cpp +++ b/src/lib/base.cpp @@ -14,6 +14,17 @@ #include #include #include +#include + +// Forward declaration for WebSocket user data +struct WebSocketUserData { + std::string pendingMessage; + std::string token; + std::string userName; + std::string userColor; + std::string currentRoom; + bool authenticated; +}; namespace Yc { namespace Lib { @@ -327,5 +338,42 @@ namespace Yc { return token; } + void Base::sendWebSocketMessage(void* wsi, const std::string& out) { + #ifdef YC_DEBUG + std::cout << "[Debug] Base::sendWebSocketMessage(void*) called with wsi: " << wsi << ", length: " << out.length() << std::endl; + #endif + + if (!wsi) { + #ifdef YC_DEBUG + std::cout << "[Debug] Invalid WebSocket wsi (nullptr), skipping send" << std::endl; + #endif + return; + } + + // Cast to lws* and use libwebsockets API + struct lws* lws_wsi = static_cast(wsi); + + // Store message in user data for sending + auto* ud = reinterpret_cast(lws_wsi_user(lws_wsi)); + if (ud) { + ud->pendingMessage = out; + lws_callback_on_writable(lws_wsi); + } else { + #ifdef YC_DEBUG + std::cout << "[Debug] No user data found for WebSocket, cannot send message" << std::endl; + #endif + } + } + + void Base::sendWebSocketMessage(void* wsi, const Json::Value& out) { + #ifdef YC_DEBUG + std::cout << "[Debug] Base::sendWebSocketMessage(void*, Json::Value) called with wsi: " << wsi << std::endl; + #endif + + // Convert JSON to string and call the string version + std::string outString = getJsonString(out); + sendWebSocketMessage(wsi, outString); + } + } // namespace Lib } // namespace Yc diff --git a/src/lib/base.h b/src/lib/base.h index 907c6d1..1fbfeb3 100755 --- a/src/lib/base.h +++ b/src/lib/base.h @@ -23,6 +23,8 @@ protected: // WebSocket helpers (server-side): read one text frame and send a text frame std::string readWebSocketMessage(int socket); void sendWebSocketMessage(int socket, const std::string& out); + void sendWebSocketMessage(void* wsi, const std::string& out); + void sendWebSocketMessage(void* wsi, const Json::Value& out); // WebSocket connection tracking and handshake helpers static void markWebSocket(int socket); static void unmarkWebSocket(int socket);