Final framework, now just cleanup

This commit is contained in:
Aaron Kimbre 2025-01-28 13:58:29 -06:00
parent ddd9ff273e
commit aedc8a09fe
6 changed files with 136 additions and 77 deletions

View File

@ -450,7 +450,6 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet) {
// build chat json data // build chat json data
nlohmann::json data; nlohmann::json data;
data["action"] = "chat";
data["playerName"] = sender.playerName; data["playerName"] = sender.playerName;
data["message"] = message.GetAsString(); data["message"] = message.GetAsString();
auto& zoneID = data["zone_id"]; auto& zoneID = data["zone_id"];
@ -460,7 +459,7 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet) {
switch (channel) { switch (channel) {
case eChatChannel::LOCAL: { case eChatChannel::LOCAL: {
Game::web.SendWSMessage("WorldChat", data.dump()); Game::web.SendWSMessage("chat_local", data);
break; break;
} }
case eChatChannel::TEAM: { case eChatChannel::TEAM: {
@ -472,7 +471,7 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet) {
if (!otherMember) return; if (!otherMember) return;
SendPrivateChatMessage(sender, otherMember, otherMember, message, eChatChannel::TEAM, eChatMessageResponseCode::SENT); SendPrivateChatMessage(sender, otherMember, otherMember, message, eChatChannel::TEAM, eChatMessageResponseCode::SENT);
data["teamID"] = team->teamID; data["teamID"] = team->teamID;
Game::web.SendWSMessage("teamchat", data.dump()); Game::web.SendWSMessage("chat_team", data);
} }
break; break;
} }
@ -528,6 +527,14 @@ void ChatPacketHandler::HandlePrivateChatMessage(Packet* packet) {
// only freinds can whispr each other // only freinds can whispr each other
for (const auto& fr : receiver.friends) { for (const auto& fr : receiver.friends) {
if (fr.friendID == sender.playerID) { 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: //To the sender:
SendPrivateChatMessage(sender, receiver, sender, message, eChatChannel::PRIVATE_CHAT, eChatMessageResponseCode::SENT); SendPrivateChatMessage(sender, receiver, sender, message, eChatChannel::PRIVATE_CHAT, eChatMessageResponseCode::SENT);
//To the receiver: //To the receiver:

View File

@ -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<std::string>();
LOG_DEBUG("type %s subscribed", type.c_str());
const auto sub = magic_enum::enum_cast<eWSSubscription>(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<std::string>();
LOG_DEBUG("type %s unsubscribed", type.c_str());
const auto sub = magic_enum::enum_cast<eWSSubscription>(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() { void ChatWeb::RegisterRoutes() {
// REST API v1 routes // REST API v1 routes
std::string v1_route = "/api/v1/"; std::string v1_route = "/api/v1/";
@ -120,19 +90,16 @@ void ChatWeb::RegisterRoutes() {
.handle = HandleHTTPAnnounceRequest .handle = HandleHTTPAnnounceRequest
}); });
// WebSocket Actions // WebSocket Events
Game::web.RegisterWSAction({ Game::web.RegisterWSEvent({
.action = "subscribe", .name = "chat",
.handle = HandleWSSubscribe
});
Game::web.RegisterWSAction({
.action = "unsubscribe",
.handle = HandleWSUnsubscribe
});
Game::web.RegisterWSAction({
.action = "chat",
.handle = HandleWSChat .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");
} }

View File

@ -6,15 +6,6 @@
#include "Web.h" #include "Web.h"
enum class eWSSubscription {
WORLD_CHAT,
PRIVATE_CHAT,
TEAM_CHAT,
TEAM,
PLAYER,
INVALID
};
namespace ChatWeb { namespace ChatWeb {
void RegisterRoutes(); void RegisterRoutes();
}; };

View File

@ -70,7 +70,7 @@ void PlayerContainer::InsertPlayer(Packet* packet) {
zoneID["map_id"] = data.zoneID.GetMapID(); zoneID["map_id"] = data.zoneID.GetMapID();
zoneID["instance_id"] = data.zoneID.GetInstanceID(); zoneID["instance_id"] = data.zoneID.GetInstanceID();
zoneID["clone_id"] = data.zoneID.GetCloneID(); 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()); Database::Get()->UpdateActivityLog(data.playerID, eActivityType::PlayerLoggedIn, data.zoneID.GetMapID());
m_PlayersToRemove.erase(playerId); m_PlayersToRemove.erase(playerId);
} }
@ -130,7 +130,7 @@ void PlayerContainer::RemovePlayer(const LWOOBJID playerID) {
wsdata["type"] = "remove"; wsdata["type"] = "remove";
wsdata["playerName"] = player.playerName; wsdata["playerName"] = player.playerName;
wsdata["playerID"] = player.playerID; wsdata["playerID"] = player.playerID;
Game::web.SendWSMessage("player", wsdata.dump()); Game::web.SendWSMessage("player", wsdata);
m_PlayerCount--; m_PlayerCount--;
LOG("Removed user: %llu", playerID); LOG("Removed user: %llu", playerID);

View File

@ -14,7 +14,8 @@ namespace Game {
namespace { namespace {
const char * json_content_type = "application/json"; const char * json_content_type = "application/json";
std::map<std::pair<eHTTPMethod, std::string>, HTTPRoute> g_HTTPRoutes; std::map<std::pair<eHTTPMethod, std::string>, HTTPRoute> g_HTTPRoutes;
std::map<std::string, WSAction> g_WSactions; std::map<std::string, WSEvent> g_WSEvents;
std::vector<std::string> g_WSSubscriptions;
} }
using json = nlohmann::json; using json = nlohmann::json;
@ -80,17 +81,17 @@ void HandleWSMessage(mg_connection* connection, const mg_ws_message* ws_msg) {
auto data = GeneralUtils::TryParse<json>(std::string(ws_msg->data.buf, ws_msg->data.len)); auto data = GeneralUtils::TryParse<json>(std::string(ws_msg->data.buf, ws_msg->data.len));
if (data) { if (data) {
const auto& good_data = data.value(); const auto& good_data = data.value();
auto check = JSONUtils::CheckRequiredData(good_data, { "action" }); auto check = JSONUtils::CheckRequiredData(good_data, { "event" });
if (!check.empty()) { if (!check.empty()) {
LOG_DEBUG("Received invalid websocket message: %s", check.c_str()); LOG_DEBUG("Received invalid websocket message: %s", check.c_str());
} else { } else {
const auto action = good_data["action"].get<std::string>(); const auto event = good_data["event"].get<std::string>();
const auto actionItr = g_WSactions.find(action); const auto eventItr = g_WSEvents.find(event);
if (actionItr != g_WSactions.end()) { if (eventItr != g_WSEvents.end()) {
const auto& [_, action] = *actionItr; const auto& [_, event] = *eventItr;
action.handle(connection, good_data); event.handle(connection, good_data);
} else { } else {
LOG_DEBUG("Received invalid websocket action: %s", action.c_str()); LOG_DEBUG("Received invalid websocket event: %s", event.c_str());
} }
} }
} else { } 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<std::string>();
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<std::string>();
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) { void HandleMessages(mg_connection* connection, int message, void* message_data) {
switch (message) { switch (message) {
case MG_EV_HTTP_MSG: case MG_EV_HTTP_MSG:
@ -121,12 +177,23 @@ void Web::RegisterHTTPRoute(HTTPRoute route) {
} }
} }
void Web::RegisterWSAction(WSAction action) { void Web::RegisterWSEvent(WSEvent event) {
auto [_, success] = g_WSactions.try_emplace(action.action, action); auto [_, success] = g_WSEvents.try_emplace(event.name, event);
if (!success) { 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 { } 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)) { if (!mg_http_listen(&mgr, listen_address.c_str(), HandleMessages, NULL)) {
LOG("Failed to create web server listener on %s", listen_address.c_str()); LOG("Failed to create web server listener on %s", listen_address.c_str());
return false; 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; return true;
} }
@ -156,10 +240,19 @@ void Web::ReceiveRequests() {
mg_mgr_poll(&mgr, 15); 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) { for (struct mg_connection *wc = Game::web.mgr.conns; wc != NULL; wc = wc->next) {
if (wc->is_websocket /* && wc->data[GeneralUtils::ToUnderlying(sub)] == 1*/) { if (wc->is_websocket && wc->data[index] == 1) {
mg_ws_send(wc, message.c_str(), message.size(), WEBSOCKET_OP_TEXT); mg_ws_send(wc, data.dump().c_str(), data.dump().size(), WEBSOCKET_OP_TEXT);
} }
} }
} }

View File

@ -28,8 +28,8 @@ struct HTTPRoute {
std::function<void(HTTPReply&, const std::string&)> handle; std::function<void(HTTPReply&, const std::string&)> handle;
}; };
struct WSAction { struct WSEvent {
std::string action; std::string name;
std::function<void(mg_connection*, nlohmann::json)> handle; std::function<void(mg_connection*, nlohmann::json)> handle;
}; };
@ -44,10 +44,11 @@ public:
Web(); Web();
~Web(); ~Web();
void ReceiveRequests(); 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); bool Startup(const std::string& listen_ip, const uint32_t listen_port);
void RegisterHTTPRoute(HTTPRoute route); void RegisterHTTPRoute(HTTPRoute route);
void RegisterWSAction(WSAction action); void RegisterWSEvent(WSEvent event);
void RegisterWSSubscription(const std::string& subscription);
private: private:
mg_mgr mgr; mg_mgr mgr;
}; };