RunicVTT Open Source Virtual Tabletop for TTRPG using P2P
Loading...
Searching...
No Matches
SignalingServer.cpp
Go to the documentation of this file.
1#include "SignalingServer.h"
2#include <rtc/rtc.hpp>
3#include <iostream>
4#include "NetworkManager.h"
5#include <nlohmann/json.hpp>
6#include "Message.h"
7#include "Logger.h"
8#include "NetworkUtilities.h"
9
10using json = nlohmann::json;
11using Clock = std::chrono::steady_clock;
12
13SignalingServer::SignalingServer(std::weak_ptr<NetworkManager> parent) :
14 network_manager(parent)
15{
16}
17
22
23void SignalingServer::start(unsigned short port)
24{
25 rtc::WebSocketServerConfiguration cfg;
26 cfg.bindAddress = "0.0.0.0";
27 cfg.port = port;
28 cfg.connectionTimeout = std::chrono::milliseconds(0);
29
30 server = std::make_shared<rtc::WebSocketServer>(cfg);
31
32 server->onClient([this](std::shared_ptr<rtc::WebSocket> ws)
33 {
34 auto addrOpt = ws->remoteAddress();
35 if (!addrOpt) { std::cout << "[SignalingServer] Client connected (no addr)\n"; return; }
36 std::string clientId = *addrOpt;
37 std::cout << "[SignalingServer] Client Connected: " << clientId << "\n";
38
39 onConnect(clientId, ws);
40
41 ws->onMessage([this, clientId](std::variant<rtc::binary, rtc::string> msg) {
42 std::cout << "[SignalingServer] MESSAGE RECEIVED" <<"\n";
43 if (!std::holds_alternative<rtc::string>(msg)) return;
44 const auto& s = std::get<rtc::string>(msg);
45 std::cout << "[SignalingServer] MESSAGE: " << s <<"\n";
46 //prunePending();
47 onMessage(clientId, s);
48 });
49
50 ws->onError([clientId](std::string err) {
51 std::cout << "[SignalingServer] Client Error (" << clientId << "): " << err << "\n";
52 });
53
54 ws->onClosed([this, clientId]() {
55 std::cout << "[SignalingServer] Client disconnected: " << clientId << "\n";
56 pendingClients_.erase(clientId);
57 //pendingSince_.erase(clientId);
58 authClients_.erase(clientId);
59 }); });
60
61 is_running = true;
62 std::cout << "[SignalingServer] Listening at ws://0.0.0.0:" << port << "\n";
63}
64
66{
67 if (server)
68 {
69 server->stop();
70 server.reset();
71 is_running = false;
72 }
73 for (auto [id, ws] : pendingClients_)
74 {
76 }
77
78 for (auto [id, ws] : authClients_)
79 {
81 }
82 pendingClients_.clear();
83 authClients_.clear();
84}
85
86void SignalingServer::onConnect(std::string clientId, std::shared_ptr<rtc::WebSocket> ws)
87{
88 pendingClients_[clientId] = std::move(ws);
89 //pendingSince_[clientId] = Clock::now();
90}
91
92void SignalingServer::onMessage(const std::string& clientId, const std::string& text)
93{
94
95 json j;
96 try
97 {
98 j = json::parse(text);
99 }
100 catch (...)
101 {
102 return;
103 }
104
105 const std::string type = j.value(msg::key::Type, "");
106 if (type.empty())
107 return;
108
109 if (type == msg::signaling::Auth)
110 {
111 auto nm = network_manager.lock();
112 if (!nm)
113 throw std::runtime_error("NetworkManager expired");
114
115 const std::string provided = j.value(std::string(msg::key::AuthToken), "");
116 const std::string expected = nm->getNetworkPassword();
117
118 const bool ok = (expected.empty() || provided == expected);
119 const std::string username = j.value(std::string(msg::key::Username), "guest" + clientId);
120 const std::string clientUniqueId = j.value(std::string(msg::key::UniqueId), "");
121
122 if (ok)
123 {
124 moveToAuthenticated(clientId);
125
126 std::vector<std::string> others;
127 others.reserve(authClients_.size());
128 for (auto& [id, _] : authClients_)
129 if (id != clientId)
130 others.emplace_back(id);
131
132 // IMPORTANT: GM id in response must be GM UNIQUE ID
133 const std::string gmUniqueId = nm->getMyUniqueId();
134
135 // you may optionally echo client uniqueId back (makeAuthResponse supports uniqueId param)
136 auto resp = msg::makeAuthResponse(msg::value::True, "welcome",
137 clientId, username,
138 others,
139 /*gmPeerId=*/gmUniqueId,
140 /*uniqueId=*/clientUniqueId);
141 sendTo(clientId, resp.dump());
142 }
143 else
144 {
145 auto resp = msg::makeAuthResponse(msg::value::False, "invalid password",
146 clientId, username);
147 sendTo(clientId, resp.dump());
148
149 if (auto it = pendingClients_.find(clientId); it != pendingClients_.end() && it->second)
150 it->second->close();
151 else if (auto it2 = authClients_.find(clientId); it2 != authClients_.end() && it2->second)
152 it2->second->close();
153 }
154 return;
155 }
156
157 // for all other types, require auth
158 if (!isAuthenticated(clientId))
159 {
160 auto resp = msg::makeAuthResponse(msg::value::False, "unauthenticated",
161 clientId, "guest" + clientId);
162 sendTo(clientId, resp.dump());
163 return;
164 }
165
166 // Router: overwrite from with server-trusted clientId
167 j[msg::key::From] = clientId;
168
169 // In SignalingServer::onMessage (after auth checks)
171 {
172 const std::string target = j.value(std::string(msg::key::Target), "");
173 if (target.empty())
174 return;
175
176 // Broadcast to all authed clients (including target)
177 const std::string dump = j.dump();
178 for (auto& [id, ws] : authClients_)
179 {
180 if (ws && !ws->isClosed())
181 ws->send(dump);
182 }
183
184 // Optionally kick the target at signaling level
185 if (auto it = authClients_.find(target); it != authClients_.end())
186 {
187 if (it->second)
188 it->second->close();
189 }
190 return;
191 }
192
193 // Broadcast to authenticated only
195 {
196 const std::string dump = j.dump();
197 for (auto& [id, ws] : authClients_)
198 {
199 if (id == clientId)
200 continue; // don't echo to sender
201 if (ws && !ws->isClosed())
202 ws->send(dump);
203 }
204 return;
205 }
206
207 // Direct
208 const std::string to = j.value(msg::key::To, "");
209 if (to.empty())
210 return;
211 auto it = authClients_.find(to);
212 if (it != authClients_.end() && it->second && !it->second->isClosed())
213 {
214 it->second->send(j.dump());
215 }
216}
217
218void SignalingServer::sendTo(const std::string& clientId, const std::string& message)
219{
220 if (auto it = authClients_.find(clientId); it != authClients_.end() && it->second && !it->second->isClosed())
221 {
222 it->second->send(message);
223 return;
224 }
225 if (auto it = pendingClients_.find(clientId); it != pendingClients_.end() && it->second && !it->second->isClosed())
226 {
227 it->second->send(message);
228 }
229}
230
231void SignalingServer::broadcast(const std::string& message)
232{
233 for (auto& [id, ws] : authClients_)
234 {
235 if (ws && !ws->isClosed())
236 ws->send(message);
237 }
238}
239
241{
242 nlohmann::json j = msg::makeBroadcastShutdown();
243 broadcast(j.dump());
244}
245
247{
248 // Move out both maps to avoid erase while inside callbacks
249 auto auth = std::move(authClients_);
250 auto pend = std::move(pendingClients_);
251 authClients_.clear();
252 pendingClients_.clear();
253
254 auto closer = [](auto& map)
255 {
256 for (auto& [id, ws] : map)
257 {
258 if (!ws)
259 continue;
260 try
261 {
262 ws->onOpen(nullptr);
263 ws->onMessage(nullptr);
264 ws->onClosed(nullptr);
265 ws->onError(nullptr);
266 ws->close();
267 }
268 catch (...)
269 {
270 Logger::instance().log("main", Logger::Level::Warn, "WS close failed for " + id);
271 }
272 }
273 map.clear();
274 };
275
276 closer(auth);
277 closer(pend);
278}
279
280void SignalingServer::moveToAuthenticated(const std::string& clientId)
281{
282 auto it = pendingClients_.find(clientId);
283 if (it == pendingClients_.end())
284 return;
285 authClients_[clientId] = it->second;
286 pendingClients_.erase(it);
287 //pendingSince_.erase(clientId);
288 std::cout << "[SignalingServer] Authenticated: " << clientId << "\n";
289}
290
291bool SignalingServer::isAuthenticated(const std::string& clientId) const
292{
293 return authClients_.find(clientId) != authClients_.end();
294}
295
296void SignalingServer::disconnectClient(const std::string& clientId)
297{
298 auto it = authClients_.find(clientId);
299 if (it != authClients_.end() && it->second)
300 {
301 if (!it->second->isClosed())
302 it->second->close();
303 }
304}
std::chrono::steady_clock Clock
nlohmann::json json
static Logger & instance()
Definition Logger.h:39
static void safeCloseWebSocket(std::shared_ptr< rtc::WebSocket > &ws)
std::shared_ptr< rtc::WebSocketServer > server
void onMessage(const std::string &clientId, const std::string &text)
SignalingServer(std::weak_ptr< NetworkManager > parent)
void broadcast(const std::string &message)
bool isAuthenticated(const std::string &clientId) const
void disconnectClient(const std::string &clientId)
std::unordered_map< std::string, std::shared_ptr< rtc::WebSocket > > pendingClients_
void onConnect(std::string clientId, std::shared_ptr< rtc::WebSocket > client)
void moveToAuthenticated(const std::string &clientId)
void start(unsigned short port)
std::weak_ptr< NetworkManager > network_manager
std::unordered_map< std::string, std::shared_ptr< rtc::WebSocket > > authClients_
void sendTo(const std::string &clientId, const std::string &message)
constexpr std::string_view AuthToken
Definition Message.h:251
constexpr std::string_view Type
Definition Message.h:229
constexpr std::string_view Target
Definition Message.h:239
constexpr std::string_view To
Definition Message.h:231
constexpr std::string_view UniqueId
Definition Message.h:248
constexpr std::string_view From
Definition Message.h:230
constexpr std::string_view Username
Definition Message.h:237
constexpr std::string_view Broadcast
Definition Message.h:232
constexpr std::string_view PeerDisconnect
Definition Message.h:282
constexpr std::string_view Auth
Definition Message.h:278
constexpr std::string False
Definition Message.h:287
constexpr std::string True
Definition Message.h:288
Definition Message.h:28
Json makeBroadcastShutdown()
Definition Message.h:474
nlohmann::json makeAuthResponse(const std::string ok, const std::string &msg, const std::string &clientId, const std::string &username, const std::vector< std::string > &clients={}, const std::string &gmPeerId="", const std::string &uniqueId="")
Definition Message.h:506