ORE Studio 0.0.4
Loading...
Searching...
No Matches
client_session.hpp
1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 *
3 * Copyright (C) 2025 Marco Craveiro <marco.craveiro@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or modify it under
6 * the terms of the GNU General Public License as published by the Free Software
7 * Foundation; either version 3 of the License, or (at your option) any later
8 * version.
9 *
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
13 * details.
14 *
15 * You should have received a copy of the GNU General Public License along with
16 * this program; if not, write to the Free Software Foundation, Inc., 51
17 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 *
19 */
20#ifndef ORES_COMMS_NET_CLIENT_SESSION_HPP
21#define ORES_COMMS_NET_CLIENT_SESSION_HPP
22
23#include <mutex>
24#include <deque>
25#include <memory>
26#include <chrono>
27#include <optional>
28#include <expected>
29#include <functional>
30#include <string>
31#include <vector>
32#include <set>
33#include <boost/uuid/uuid.hpp>
34#include "ores.logging/make_logger.hpp"
35#include "ores.comms/messaging/message_types.hpp"
36#include "ores.comms/messaging/message_traits.hpp"
37#include "ores.comms/messaging/frame.hpp"
38#include "ores.comms/messaging/error_protocol.hpp"
39#include "ores.comms/net/client.hpp"
40#include "ores.comms/net/client_options.hpp"
41
42namespace ores::comms::service {
43
44class remote_event_adapter;
45
46}
47
48namespace ores::comms::net {
49
57 boost::uuids::uuid account_id;
58 std::string username;
59 std::string email;
60};
61
68 std::string event_type;
69 std::chrono::system_clock::time_point timestamp;
70 std::vector<std::string> entity_ids;
71};
72
77 not_connected,
78 not_logged_in,
79 login_required,
80 admin_required,
81 request_failed,
82 deserialization_failed,
83 server_error,
84 connection_lost
85};
86
94 std::string message;
95
97 : code(c) {}
98
99 session_error(client_session_error c, std::string msg)
100 : code(c), message(std::move(msg)) {}
101};
102
103template<typename Request>
104concept Serializable = requires(Request req) {
105 { req.serialize() } -> std::convertible_to<std::vector<std::byte>>;
106};
107
108template<typename Response>
109concept Deserializable = requires(std::span<const std::byte> data) {
110 {
111 Response::deserialize(data)
112 } -> std::same_as<std::expected<Response, ores::utility::serialization::error_code>>;
113};
114
125class client_session final {
126private:
127 inline static std::string_view logger_name =
128 "ores.comms.net.client_session";
129
130 static auto& lg() {
131 using namespace ores::logging;
132 static auto instance = make_logger(logger_name);
133 return instance;
134 }
135
136public:
139
140 // Non-copyable, non-movable (contains mutex)
141 client_session(const client_session&) = delete;
142 client_session& operator=(const client_session&) = delete;
143 client_session(client_session&&) = delete;
144 client_session& operator=(client_session&&) = delete;
145
155 std::expected<void, session_error> connect(client_options options);
156
167 std::expected<void, session_error>
168 attach_client(std::shared_ptr<client> external_client);
169
177 void disconnect();
178
186 void detach_client();
187
191 [[nodiscard]] bool is_connected() const noexcept;
192
199 [[nodiscard]] std::shared_ptr<client> get_client() const noexcept {
200 return client_;
201 }
202
206 [[nodiscard]] bool is_logged_in() const noexcept {
207 return session_info_.has_value();
208 }
209
218 session_info_ = std::move(info);
219 }
220
226 void clear_session_info() noexcept {
227 session_info_.reset();
228 }
229
233 [[nodiscard]] const std::optional<client_session_info>& session_info() const noexcept {
234 return session_info_;
235 }
236
240 [[nodiscard]] std::string username() const noexcept {
241 return session_info_.has_value() ? session_info_->username : std::string{};
242 }
243
247 [[nodiscard]] std::string email() const noexcept {
248 return session_info_.has_value() ? session_info_->email : std::string{};
249 }
250
256 void set_email(const std::string& email) {
257 if (session_info_.has_value()) {
258 session_info_->email = email;
259 }
260 }
261
265 [[nodiscard]] std::optional<boost::uuids::uuid> account_id() const noexcept {
266 if (session_info_.has_value()) {
267 return session_info_->account_id;
268 }
269 return std::nullopt;
270 }
271
283 template <Serializable RequestType,
284 Deserializable ResponseType,
285 messaging::message_type RequestMsgType>
286 std::expected<ResponseType, session_error>
287 process_request(RequestType request) {
288 using namespace ores::logging;
289
290 if (!client_ || !client_->is_connected()) {
291 BOOST_LOG_SEV(lg(), error) << "Not connected to server";
292 return std::unexpected(session_error(client_session_error::not_connected));
293 }
294
295 BOOST_LOG_SEV(lg(), debug) << "Processing request type: "
296 << RequestMsgType;
297
298 auto payload = request.serialize();
299 messaging::frame request_frame(RequestMsgType, 0, std::move(payload));
300
301 auto response_result = client_->send_request_sync(std::move(request_frame));
302
303 if (!response_result) {
304 auto error_code = response_result.error();
305 BOOST_LOG_SEV(lg(), error) << "Request failed with error code: "
306 << static_cast<int>(error_code);
307 // Check if this is a network error indicating connection loss
308 if (error_code == ores::utility::serialization::error_code::network_error) {
309 return std::unexpected(session_error(
310 client_session_error::connection_lost,
311 "Connection to server lost"));
312 }
313 return std::unexpected(session_error(
314 client_session_error::request_failed,
315 "Request failed: " + messaging::to_string(error_code)));
316 }
317
318 auto decompressed = response_result->decompressed_payload();
319 if (!decompressed) {
320 BOOST_LOG_SEV(lg(), error) << "Failed to decompress response payload";
321 return std::unexpected(session_error(client_session_error::deserialization_failed));
322 }
323
324 // Check for error response
325 if (response_result->header().type == messaging::message_type::error_response) {
326 auto err_resp = messaging::error_response::deserialize(*decompressed);
327 if (err_resp) {
328 BOOST_LOG_SEV(lg(), error) << "Server returned error: "
329 << err_resp->message;
330 return std::unexpected(session_error(
331 client_session_error::server_error,
332 err_resp->message));
333 }
334 return std::unexpected(session_error(client_session_error::server_error));
335 }
336
337 auto response = ResponseType::deserialize(*decompressed);
338 if (!response) {
339 BOOST_LOG_SEV(lg(), error) << "Failed to deserialize response";
340 return std::unexpected(session_error(client_session_error::deserialization_failed));
341 }
342
343 BOOST_LOG_SEV(lg(), debug) << "Successfully processed request";
344 return std::move(*response);
345 }
346
358 template <Serializable RequestType,
359 Deserializable ResponseType,
360 messaging::message_type RequestMsgType>
361 std::expected<ResponseType, session_error>
362 process_authenticated_request(RequestType request) {
363 using namespace ores::logging;
364 if (!is_logged_in()) {
365 BOOST_LOG_SEV(lg(), warn) << "Attempted authenticated request while "
366 << "not logged in";
367 return std::unexpected(session_error(client_session_error::not_logged_in));
368 }
369 return process_request<RequestType, ResponseType, RequestMsgType>(
370 std::move(request));
371 }
372
387 template <Serializable RequestType,
388 Deserializable ResponseType,
389 messaging::message_type RequestMsgType>
390 [[deprecated("Permission checks are now server-side via RBAC")]]
391 std::expected<ResponseType, session_error>
392 process_admin_request(RequestType request) {
393 // Permission checks now happen server-side via RBAC
394 return process_authenticated_request<RequestType, ResponseType, RequestMsgType>(
395 std::move(request));
396 }
397
398 // =========================================================================
399 // Traits-based process_request overloads
400 // =========================================================================
401 // These overloads use message_traits to infer the response type and
402 // message_type enum from the request type, simplifying the API.
403
414 template <typename RequestType>
416 std::expected<typename messaging::message_traits<RequestType>::response_type,
418 process_request(RequestType request) {
420 return process_request<
421 RequestType,
422 typename traits::response_type,
423 traits::request_message_type>(std::move(request));
424 }
425
433 template <typename RequestType>
435 std::expected<typename messaging::message_traits<RequestType>::response_type,
437 process_authenticated_request(RequestType request) {
440 RequestType,
441 typename traits::response_type,
442 traits::request_message_type>(std::move(request));
443 }
444
455 template <typename RequestType>
457 [[deprecated("Permission checks are now server-side via RBAC")]]
458 std::expected<typename messaging::message_traits<RequestType>::response_type,
460 process_admin_request(RequestType request) {
461 // Permission checks now happen server-side via RBAC
462 return process_authenticated_request(std::move(request));
463 }
464
474 bool subscribe(const std::string& event_type);
475
484 bool unsubscribe(const std::string& event_type);
485
492 [[nodiscard]] bool is_subscribed(const std::string& event_type) const;
493
499 [[nodiscard]] std::set<std::string> get_subscriptions() const;
500
509 std::vector<pending_notification> take_pending_notifications();
510
516 [[nodiscard]] bool has_pending_notifications() const;
517
521 using notification_callback_t = std::function<void(
522 const std::string& event_type,
523 std::chrono::system_clock::time_point timestamp,
524 const std::vector<std::string>& entity_ids)>;
525
537
538private:
545 void on_notification(const std::string& event_type,
546 std::chrono::system_clock::time_point timestamp,
547 const std::vector<std::string>& entity_ids);
548
549 std::shared_ptr<client> client_;
550 std::unique_ptr<service::remote_event_adapter> event_adapter_;
551 std::optional<client_session_info> session_info_;
552 mutable std::mutex notifications_mutex_;
553 std::deque<pending_notification> pending_notifications_;
554
555 // External notification callback (if set, replaces internal queuing)
556 notification_callback_t external_notification_callback_;
557
558 // True when client is externally managed (attached via attach_client).
559 // In this mode, disconnect() will not call client_->disconnect().
560 bool external_client_{false};
561};
562
566std::string to_string(client_session_error error);
567
574std::string to_string(const session_error& error);
575
576}
577
578#endif
std::string to_string(ores::utility::serialization::error_code ec)
Convert error_code to string for display.
Definition message_types.hpp:592
Main server application for ORE Studio.
Definition application.hpp:30
Contains the networking elements of the comms library.
Definition client.hpp:48
std::string to_string(client_session_error error)
Convert client_session_error to string for display.
Definition client_session.cpp:242
client_session_error
Error codes specific to client session operations.
Definition client_session.hpp:76
Implements logging infrastructure for ORE Studio.
Definition boost_severity.hpp:28
static std::expected< error_response, ores::utility::serialization::error_code > deserialize(std::span< const std::byte > data)
Deserialize from frame payload.
Definition handshake.cpp:113
Complete frame with header and payload.
Definition frame.hpp:77
Traits template for mapping request types to their response types and message type enum values.
Definition message_traits.hpp:66
Configuration for the client.
Definition client_options.hpp:78
Information about the client's authenticated session.
Definition client_session.hpp:56
A notification received from the server.
Definition client_session.hpp:67
Error information returned from client session operations.
Definition client_session.hpp:92
Client-side session manager providing auth-aware request handling.
Definition client_session.hpp:125
std::function< void(const std::string &event_type, std::chrono::system_clock::time_point timestamp, const std::vector< std::string > &entity_ids)> notification_callback_t
Notification callback function type.
Definition client_session.hpp:524
void set_notification_callback(notification_callback_t callback)
Set an external notification callback.
Definition client_session.cpp:238
bool is_connected() const noexcept
Check if connected to server.
Definition client_session.cpp:172
bool unsubscribe(const std::string &event_type)
Unsubscribe from notifications for an event type.
Definition client_session.cpp:185
std::expected< typename messaging::message_traits< RequestType >::response_type, session_error > process_admin_request(RequestType request)
Process a request using message_traits (requires admin).
Definition client_session.hpp:460
const std::optional< client_session_info > & session_info() const noexcept
Get current session info if logged in.
Definition client_session.hpp:233
bool subscribe(const std::string &event_type)
Subscribe to notifications for an event type.
Definition client_session.cpp:176
std::expected< ResponseType, session_error > process_admin_request(RequestType request)
Process a request that requires admin privileges.
Definition client_session.hpp:392
std::optional< boost::uuids::uuid > account_id() const noexcept
Get the account ID if logged in.
Definition client_session.hpp:265
std::set< std::string > get_subscriptions() const
Get the set of currently subscribed event types.
Definition client_session.cpp:201
std::string username() const noexcept
Get the current username if logged in.
Definition client_session.hpp:240
std::vector< pending_notification > take_pending_notifications()
Get all pending notifications and clear the queue.
Definition client_session.cpp:208
std::shared_ptr< client > get_client() const noexcept
Get the underlying client.
Definition client_session.hpp:199
bool is_logged_in() const noexcept
Check if logged in.
Definition client_session.hpp:206
void clear_session_info() noexcept
Clear session info on logout.
Definition client_session.hpp:226
std::expected< typename messaging::message_traits< RequestType >::response_type, session_error > process_request(RequestType request)
Process a request using message_traits (does not require auth).
Definition client_session.hpp:418
void disconnect()
Disconnect from the server.
Definition client_session.cpp:139
void set_session_info(client_session_info info)
Set session info after successful login.
Definition client_session.hpp:217
std::expected< void, session_error > attach_client(std::shared_ptr< client > external_client)
Attach an external client to this session.
Definition client_session.cpp:81
std::string email() const noexcept
Get the current email if logged in.
Definition client_session.hpp:247
std::expected< ResponseType, session_error > process_authenticated_request(RequestType request)
Process a request that requires authentication.
Definition client_session.hpp:362
std::expected< ResponseType, session_error > process_request(RequestType request)
Process a request that does not require authentication.
Definition client_session.hpp:287
void set_email(const std::string &email)
Set the current email.
Definition client_session.hpp:256
bool is_subscribed(const std::string &event_type) const
Check if currently subscribed to an event type.
Definition client_session.cpp:194
std::expected< typename messaging::message_traits< RequestType >::response_type, session_error > process_authenticated_request(RequestType request)
Process a request using message_traits (requires authentication).
Definition client_session.hpp:437
std::expected< void, session_error > connect(client_options options)
Connect to the server.
Definition client_session.cpp:36
bool has_pending_notifications() const
Check if there are any pending notifications.
Definition client_session.cpp:218
void detach_client()
Detach an externally-attached client.
Definition client_session.cpp:121
Concept for types that have message_traits specialization.
Definition message_traits.hpp:82