diff --git a/dChatServer/ChatPacketHandler.cpp b/dChatServer/ChatPacketHandler.cpp index 5f05c594..39f49944 100644 --- a/dChatServer/ChatPacketHandler.cpp +++ b/dChatServer/ChatPacketHandler.cpp @@ -450,7 +450,6 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet) { // build chat json data nlohmann::json data; - data["action"] = "chat"; data["playerName"] = sender.playerName; data["message"] = message.GetAsString(); auto& zoneID = data["zone_id"]; @@ -460,7 +459,7 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet) { switch (channel) { case eChatChannel::LOCAL: { - Game::web.SendWSMessage("WorldChat", data.dump()); + Game::web.SendWSMessage("chat_local", data); break; } case eChatChannel::TEAM: { @@ -472,7 +471,7 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet) { if (!otherMember) return; SendPrivateChatMessage(sender, otherMember, otherMember, message, eChatChannel::TEAM, eChatMessageResponseCode::SENT); data["teamID"] = team->teamID; - Game::web.SendWSMessage("teamchat", data.dump()); + Game::web.SendWSMessage("chat_team", data); } break; } @@ -528,6 +527,14 @@ void ChatPacketHandler::HandlePrivateChatMessage(Packet* packet) { // only freinds can whispr each other for (const auto& fr : receiver.friends) { if (fr.friendID == sender.playerID) { + nlohmann::json data; + data["sender"] = sender.playerName; + data["receiver"] = receiverName; + data["message"] = message.GetAsString(); + data["zone_id"]["map_id"] = sender.zoneID.GetMapID(); + data["zone_id"]["instance_id"] = sender.zoneID.GetInstanceID(); + data["zone_id"]["clone_id"] = sender.zoneID.GetCloneID(); + Game::web.SendWSMessage("chat_private", data); //To the sender: SendPrivateChatMessage(sender, receiver, sender, message, eChatChannel::PRIVATE_CHAT, eChatMessageResponseCode::SENT); //To the receiver: diff --git a/dChatServer/ChatWeb.cpp b/dChatServer/ChatWeb.cpp index 62022b63..9ff1d587 100644 --- a/dChatServer/ChatWeb.cpp +++ b/dChatServer/ChatWeb.cpp @@ -69,36 +69,6 @@ void HandleWSChat(mg_connection* connection, json data) { } } -void HandleWSSubscribe(mg_connection* connection, json data) { - auto check = JSONUtils::CheckRequiredData(data, { "type" }); - if (!check.empty()) { - LOG_DEBUG("Received invalid websocket message: %s", check.c_str()); - } else { - const auto type = data["type"].get(); - LOG_DEBUG("type %s subscribed", type.c_str()); - const auto sub = magic_enum::enum_cast(type).value_or(eWSSubscription::INVALID); - if (sub != eWSSubscription::INVALID) { - connection->data[GeneralUtils::ToUnderlying(sub)] = 1; - mg_ws_send(connection, "{\"status\":\"subscribed\"}", 18, WEBSOCKET_OP_TEXT); - } - } -} - -void HandleWSUnsubscribe(mg_connection* connection, json data) { - auto check = JSONUtils::CheckRequiredData(data, { "type" }); - if (!check.empty()) { - LOG_DEBUG("Received invalid websocket message: %s", check.c_str()); - } else { - const auto type = data["type"].get(); - LOG_DEBUG("type %s unsubscribed", type.c_str()); - const auto sub = magic_enum::enum_cast(type).value_or(eWSSubscription::INVALID); - if (sub != eWSSubscription::INVALID) { - connection->data[GeneralUtils::ToUnderlying(sub)] = 0; - mg_ws_send(connection, "{\"status\":\"unsubscribed\"}", 18, WEBSOCKET_OP_TEXT); - } - } -} - void ChatWeb::RegisterRoutes() { // REST API v1 routes std::string v1_route = "/api/v1/"; @@ -120,19 +90,16 @@ void ChatWeb::RegisterRoutes() { .handle = HandleHTTPAnnounceRequest }); - // WebSocket Actions - Game::web.RegisterWSAction({ - .action = "subscribe", - .handle = HandleWSSubscribe - }); - - Game::web.RegisterWSAction({ - .action = "unsubscribe", - .handle = HandleWSUnsubscribe - }); - - Game::web.RegisterWSAction({ - .action = "chat", + // WebSocket Events + Game::web.RegisterWSEvent({ + .name = "chat", .handle = HandleWSChat }); + + // WebSocket subscriptions + Game::web.RegisterWSSubscription("chat_local"); + Game::web.RegisterWSSubscription("chat_team"); + Game::web.RegisterWSSubscription("chat_private"); + Game::web.RegisterWSSubscription("player"); + Game::web.RegisterWSSubscription("team"); } diff --git a/dChatServer/ChatWeb.h b/dChatServer/ChatWeb.h index 70a8e81c..c2a28bc9 100644 --- a/dChatServer/ChatWeb.h +++ b/dChatServer/ChatWeb.h @@ -6,15 +6,6 @@ #include "Web.h" -enum class eWSSubscription { - WORLD_CHAT, - PRIVATE_CHAT, - TEAM_CHAT, - TEAM, - PLAYER, - INVALID -}; - namespace ChatWeb { void RegisterRoutes(); }; diff --git a/dChatServer/PlayerContainer.cpp b/dChatServer/PlayerContainer.cpp index bd33871b..82973ae8 100644 --- a/dChatServer/PlayerContainer.cpp +++ b/dChatServer/PlayerContainer.cpp @@ -70,7 +70,7 @@ void PlayerContainer::InsertPlayer(Packet* packet) { zoneID["map_id"] = data.zoneID.GetMapID(); zoneID["instance_id"] = data.zoneID.GetInstanceID(); zoneID["clone_id"] = data.zoneID.GetCloneID(); - Game::web.SendWSMessage("player", wsdata.dump()); + Game::web.SendWSMessage("player", wsdata); Database::Get()->UpdateActivityLog(data.playerID, eActivityType::PlayerLoggedIn, data.zoneID.GetMapID()); m_PlayersToRemove.erase(playerId); } @@ -130,7 +130,7 @@ void PlayerContainer::RemovePlayer(const LWOOBJID playerID) { wsdata["type"] = "remove"; wsdata["playerName"] = player.playerName; wsdata["playerID"] = player.playerID; - Game::web.SendWSMessage("player", wsdata.dump()); + Game::web.SendWSMessage("player", wsdata); m_PlayerCount--; LOG("Removed user: %llu", playerID); diff --git a/dWeb/Web.cpp b/dWeb/Web.cpp index 33b0a96d..f0ca773c 100644 --- a/dWeb/Web.cpp +++ b/dWeb/Web.cpp @@ -14,7 +14,8 @@ namespace Game { namespace { const char * json_content_type = "application/json"; std::map, HTTPRoute> g_HTTPRoutes; - std::map g_WSactions; + std::map g_WSEvents; + std::vector g_WSSubscriptions; } using json = nlohmann::json; @@ -80,17 +81,17 @@ void HandleWSMessage(mg_connection* connection, const mg_ws_message* ws_msg) { auto data = GeneralUtils::TryParse(std::string(ws_msg->data.buf, ws_msg->data.len)); if (data) { const auto& good_data = data.value(); - auto check = JSONUtils::CheckRequiredData(good_data, { "action" }); + auto check = JSONUtils::CheckRequiredData(good_data, { "event" }); if (!check.empty()) { LOG_DEBUG("Received invalid websocket message: %s", check.c_str()); } else { - const auto action = good_data["action"].get(); - const auto actionItr = g_WSactions.find(action); - if (actionItr != g_WSactions.end()) { - const auto& [_, action] = *actionItr; - action.handle(connection, good_data); + const auto event = good_data["event"].get(); + const auto eventItr = g_WSEvents.find(event); + if (eventItr != g_WSEvents.end()) { + const auto& [_, event] = *eventItr; + event.handle(connection, good_data); } else { - LOG_DEBUG("Received invalid websocket action: %s", action.c_str()); + LOG_DEBUG("Received invalid websocket event: %s", event.c_str()); } } } else { @@ -99,6 +100,61 @@ void HandleWSMessage(mg_connection* connection, const mg_ws_message* ws_msg) { } } +void HandleWSSubscribe(mg_connection* connection, json data) { + auto check = JSONUtils::CheckRequiredData(data, { "subscription" }); + if (!check.empty()) { + LOG_DEBUG("Received invalid websocket message: %s", check.c_str()); + } else { + const auto subscription = data["subscription"].get(); + LOG_DEBUG("subscription %s subscribed", subscription.c_str()); + // check subscription vector + auto subItr = std::find(g_WSSubscriptions.begin(), g_WSSubscriptions.end(), subscription); + if (subItr != g_WSSubscriptions.end()) { + // get index of subscription + auto index = std::distance(g_WSSubscriptions.begin(), subItr); + connection->data[index] = 1; + mg_ws_send(connection, "{\"status\":\"subscribed\"}", 23, WEBSOCKET_OP_TEXT); + } + } +} + +void HandleWSUnsubscribe(mg_connection* connection, json data) { + auto check = JSONUtils::CheckRequiredData(data, { "subscription" }); + if (!check.empty()) { + LOG_DEBUG("Received invalid websocket message: %s", check.c_str()); + } else { + const auto subscription = data["subscription"].get(); + LOG_DEBUG("subscription %s unsubscribed", subscription.c_str()); + // check subscription vector + auto subItr = std::find(g_WSSubscriptions.begin(), g_WSSubscriptions.end(), subscription); + if (subItr != g_WSSubscriptions.end()) { + // get index of subscription + auto index = std::distance(g_WSSubscriptions.begin(), subItr); + connection->data[index] = 0; + mg_ws_send(connection, "{\"status\":\"unsubscribed\"}", 25, WEBSOCKET_OP_TEXT); + } + } +} + +void HandleWSGetSubscriptions(mg_connection* connection, json data) { + // list subscribed and non subscribed subscriptions + json response; + // check subscription vector + for (const auto& sub : g_WSSubscriptions) { + auto subItr = std::find(g_WSSubscriptions.begin(), g_WSSubscriptions.end(), sub); + if (subItr != g_WSSubscriptions.end()) { + // get index of subscription + auto index = std::distance(g_WSSubscriptions.begin(), subItr); + if (connection->data[index] == 1) { + response["subscribed"].push_back(sub); + } else { + response["unsubscribed"].push_back(sub); + } + } + } + mg_ws_send(connection, response.dump().c_str(), response.dump().size(), WEBSOCKET_OP_TEXT); +} + void HandleMessages(mg_connection* connection, int message, void* message_data) { switch (message) { case MG_EV_HTTP_MSG: @@ -121,12 +177,23 @@ void Web::RegisterHTTPRoute(HTTPRoute route) { } } -void Web::RegisterWSAction(WSAction action) { - auto [_, success] = g_WSactions.try_emplace(action.action, action); +void Web::RegisterWSEvent(WSEvent event) { + auto [_, success] = g_WSEvents.try_emplace(event.name, event); if (!success) { - LOG_DEBUG("Failed to register WS action %s", action.action.c_str()); + LOG_DEBUG("Failed to register WS event %s", event.name.c_str()); } else { - LOG_DEBUG("Registered WS action %s", action.action.c_str()); + LOG_DEBUG("Registered WS event %s", event.name.c_str()); + } +} + +void Web::RegisterWSSubscription(const std::string& subscription) { + // check that subsction is not already in the vector + auto subItr = std::find(g_WSSubscriptions.begin(), g_WSSubscriptions.end(), subscription); + if (subItr != g_WSSubscriptions.end()) { + LOG_DEBUG("Failed to register WS subscription %s: duplicate", subscription.c_str()); + } else { + LOG_DEBUG("Registered WS subscription %s", subscription.c_str()); + g_WSSubscriptions.push_back(subscription); } } @@ -148,7 +215,24 @@ bool Web::Startup(const std::string& listen_ip, const uint32_t listen_port) { if (!mg_http_listen(&mgr, listen_address.c_str(), HandleMessages, NULL)) { LOG("Failed to create web server listener on %s", listen_address.c_str()); return false; - } + } + + // WebSocket Events + Game::web.RegisterWSEvent({ + .name = "subscribe", + .handle = HandleWSSubscribe + }); + + Game::web.RegisterWSEvent({ + .name = "unsubscribe", + .handle = HandleWSUnsubscribe + }); + + Game::web.RegisterWSEvent({ + .name = "getSubscriptions", + .handle = HandleWSGetSubscriptions + }); + return true; } @@ -156,10 +240,19 @@ void Web::ReceiveRequests() { mg_mgr_poll(&mgr, 15); } -void Web::SendWSMessage(const std::string subscription, const std::string& message) { +void Web::SendWSMessage(const std::string subscription, json& data) { + // find subscription + auto subItr = std::find(g_WSSubscriptions.begin(), g_WSSubscriptions.end(), subscription); + if (subItr == g_WSSubscriptions.end()) { + LOG_DEBUG("Failed to send WS message: subscription %s not found", subscription.c_str()); + return; + } + // tell it the event type + data["event"] = subscription; + auto index = std::distance(g_WSSubscriptions.begin(), subItr); for (struct mg_connection *wc = Game::web.mgr.conns; wc != NULL; wc = wc->next) { - if (wc->is_websocket /* && wc->data[GeneralUtils::ToUnderlying(sub)] == 1*/) { - mg_ws_send(wc, message.c_str(), message.size(), WEBSOCKET_OP_TEXT); + if (wc->is_websocket && wc->data[index] == 1) { + mg_ws_send(wc, data.dump().c_str(), data.dump().size(), WEBSOCKET_OP_TEXT); } } } \ No newline at end of file diff --git a/dWeb/Web.h b/dWeb/Web.h index da40f329..cc6db36d 100644 --- a/dWeb/Web.h +++ b/dWeb/Web.h @@ -28,8 +28,8 @@ struct HTTPRoute { std::function handle; }; -struct WSAction { - std::string action; +struct WSEvent { + std::string name; std::function handle; }; @@ -44,10 +44,11 @@ public: Web(); ~Web(); void ReceiveRequests(); - void static SendWSMessage(std::string sub, const std::string& message); + void static SendWSMessage(std::string sub, nlohmann::json& message); bool Startup(const std::string& listen_ip, const uint32_t listen_port); void RegisterHTTPRoute(HTTPRoute route); - void RegisterWSAction(WSAction action); + void RegisterWSEvent(WSEvent event); + void RegisterWSSubscription(const std::string& subscription); private: mg_mgr mgr; };