Füge Unterstützung für WebSocket-Nutzer in ChatUser und ChatRoom hinzu

- 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.
This commit is contained in:
Torsten Schulz (local)
2025-09-05 13:26:52 +02:00
parent 32774617cd
commit 228e9b7ea2
8 changed files with 228 additions and 9 deletions

View File

@@ -23,7 +23,7 @@ fi
echo "=== Schritt 1: Abhängigkeiten installieren ===" echo "=== Schritt 1: Abhängigkeiten installieren ==="
echo "Führe install_dependencies.sh aus..." echo "Führe install_dependencies.sh aus..."
./deploy/install_dependencies.sh #./deploy/install_dependencies.sh
echo "" echo ""
echo "=== Schritt 2: Anwendung bauen ===" echo "=== Schritt 2: Anwendung bauen ==="

View File

@@ -175,6 +175,74 @@ namespace Yc
return true; 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<ChatUser> newUser;
if (_database) {
// Verwende den neuen WebSocket-Konstruktor
newUser = std::make_shared<ChatUser>(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<ChatUser> user, std::string password) bool ChatRoom::addUser(std::shared_ptr<ChatUser> user, std::string password)
{ {
if (password == _password) if (password == _password)

View File

@@ -42,6 +42,7 @@ namespace Yc
void run(); void run();
std::string name(); 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, int socket);
bool addUser(std::string userName, std::string color, std::string password, void* wsi);
bool addUser(std::shared_ptr<ChatUser> user, std::string password); bool addUser(std::shared_ptr<ChatUser> user, std::string password);
bool userNameExists(std::string userName); bool userNameExists(std::string userName);
void removeUser(std::string _token, bool silent = false); void removeUser(std::string _token, bool silent = false);

View File

@@ -120,6 +120,7 @@ namespace Yc
_name(name), _name(name),
_color(color), _color(color),
_socket(socket), _socket(socket),
_wsi(nullptr),
_stop(false) _stop(false)
{ {
#ifdef YC_DEBUG #ifdef YC_DEBUG
@@ -195,6 +196,89 @@ namespace Yc
// Thread-Start erfolgt jetzt explizit per start(), nicht im Konstruktor // Thread-Start erfolgt jetzt explizit per start(), nicht im Konstruktor
} }
ChatUser::ChatUser(std::shared_ptr<ChatRoom> parent, std::string name, std::string color, void* wsi, std::shared_ptr<Database> 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<int>();
userJson["falukant_user_id"] = u["falukant_user_id"].as<int>();
userJson["display_name"] = u["display_name"].c_str();
userJson["color"] = u["color"].c_str();
userJson["show_gender"] = u["show_gender"].as<bool>();
userJson["show_age"] = u["show_age"].as<bool>();
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<int>();
userJson["falukant_user_id"] = u["falukant_user_id"].as<int>();
userJson["display_name"] = u["display_name"].c_str();
userJson["color"] = u["color"].c_str();
userJson["show_gender"] = u["show_gender"].as<bool>();
userJson["show_age"] = u["show_age"].as<bool>();
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() ChatUser::~ChatUser()
{ {
// Hinweis: Thread wird nicht im Destruktor gejoint, um Deadlocks zu vermeiden! // Hinweis: Thread wird nicht im Destruktor gejoint, um Deadlocks zu vermeiden!
@@ -370,7 +454,7 @@ namespace Yc
void ChatUser::send(std::string out) { void ChatUser::send(std::string out) {
// Prüfe ob Socket noch gültig ist // Prüfe ob Socket noch gültig ist
if (_socket < 0 || _stop) { if ((_socket < 0 && !_wsi) || _stop) {
#ifdef YC_DEBUG #ifdef YC_DEBUG
std::cout << "[Debug] Skipping send - socket invalid (" << _socket << ") or user stopped (" << _stop << ") for user: " << _name << std::endl; std::cout << "[Debug] Skipping send - socket invalid (" << _socket << ") or user stopped (" << _stop << ") for user: " << _name << std::endl;
#endif #endif
@@ -378,17 +462,24 @@ namespace Yc
} }
#ifdef YC_DEBUG #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 #endif
// Entferne ggf. Token-Felder aus JSON-Strings und sende über Socket/WebSocket // Entferne ggf. Token-Felder aus JSON-Strings und sende über Socket/WebSocket
Base::sanitizeTokensInString(out); Base::sanitizeTokensInString(out);
if (_wsi) {
// WebSocket: Sende über libwebsockets
Base::sendWebSocketMessage(_wsi, out);
} else {
// Normaler Socket
Base::send(_socket, out); Base::send(_socket, out);
} }
}
void ChatUser::send(Json::Value out) { void ChatUser::send(Json::Value out) {
// Prüfe ob Socket noch gültig ist // Prüfe ob Socket noch gültig ist
if (_socket < 0 || _stop) { if ((_socket < 0 && !_wsi) || _stop) {
#ifdef YC_DEBUG #ifdef YC_DEBUG
std::cout << "[Debug] Skipping send - socket invalid (" << _socket << ") or user stopped (" << _stop << ") for user: " << _name << std::endl; std::cout << "[Debug] Skipping send - socket invalid (" << _socket << ") or user stopped (" << _stop << ") for user: " << _name << std::endl;
#endif #endif
@@ -396,13 +487,20 @@ namespace Yc
} }
#ifdef YC_DEBUG #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 #endif
// Entferne rekursiv alle Token-Felder und sende über Socket/WebSocket // Entferne rekursiv alle Token-Felder und sende über Socket/WebSocket
Base::sanitizeTokens(out); Base::sanitizeTokens(out);
if (_wsi) {
// WebSocket: Sende über libwebsockets
Base::sendWebSocketMessage(_wsi, out);
} else {
// Normaler Socket
Base::send(_socket, out); Base::send(_socket, out);
} }
}
void ChatUser::handleMessage(std::string message) void ChatUser::handleMessage(std::string message)
{ {

View File

@@ -36,6 +36,7 @@ namespace Yc
ChatUser(std::shared_ptr<ChatRoom> parent, std::string name, std::string color, int socket); ChatUser(std::shared_ptr<ChatRoom> parent, std::string name, std::string color, int socket);
ChatUser(std::shared_ptr<ChatRoom> parent, std::string name, std::string color, int socket, std::shared_ptr<Database> database); ChatUser(std::shared_ptr<ChatRoom> parent, std::string name, std::string color, int socket, std::shared_ptr<Database> database);
ChatUser(std::shared_ptr<ChatRoom> parent, std::string name, std::string color, void* wsi, std::shared_ptr<Database> database);
~ChatUser(); ~ChatUser();
std::string name() const; std::string name() const;
std::string getToken() const; std::string getToken() const;
@@ -59,6 +60,7 @@ namespace Yc
std::string _name; std::string _name;
std::string _color; std::string _color;
int _socket; int _socket;
void* _wsi; // WebSocket pointer for libwebsockets
std::string _token; std::string _token;
bool _stop; bool _stop;

View File

@@ -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; std::cout << "[YourChat] Found room '" << room << "', attempting to add user..." << std::endl;
// Add user to room (ChatUser will be created by addUser) // Add user to room (ChatUser will be created by addUser)
#ifdef YC_DEBUG #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 #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; std::cout << "[YourChat] Successfully added user '" << name << "' to room '" << room << "'" << std::endl;
// Find the created ChatUser // Find the created ChatUser
auto chatUser = roomObj->findUserByName(name); auto chatUser = roomObj->findUserByName(name);

View File

@@ -14,6 +14,17 @@
#include <openssl/buffer.h> #include <openssl/buffer.h>
#include <set> #include <set>
#include <mutex> #include <mutex>
#include <libwebsockets.h>
// 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 Yc {
namespace Lib { namespace Lib {
@@ -327,5 +338,42 @@ namespace Yc {
return token; 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<struct lws*>(wsi);
// Store message in user data for sending
auto* ud = reinterpret_cast<WebSocketUserData*>(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 Lib
} // namespace Yc } // namespace Yc

View File

@@ -23,6 +23,8 @@ protected:
// WebSocket helpers (server-side): read one text frame and send a text frame // WebSocket helpers (server-side): read one text frame and send a text frame
std::string readWebSocketMessage(int socket); std::string readWebSocketMessage(int socket);
void sendWebSocketMessage(int socket, const std::string& out); 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 // WebSocket connection tracking and handshake helpers
static void markWebSocket(int socket); static void markWebSocket(int socket);
static void unmarkWebSocket(int socket); static void unmarkWebSocket(int socket);