Radix Relay
Hybrid mesh communications with Signal Protocol encryption
Loading...
Searching...
No Matches
request_tracker.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <any>
4#include <boost/asio.hpp>
5#include <chrono>
6#include <functional>
7#include <memory>
8#include <nostr/protocol.hpp>
9#include <string>
10#include <unordered_map>
11
12namespace radix_relay::nostr {
13
21{
22private:
24 struct pending_request
25 {
26 std::function<void(const std::any &)> callback;
27 std::shared_ptr<boost::asio::steady_timer> timer;
28 };
29
30public:
36 explicit request_tracker(const std::shared_ptr<boost::asio::io_context> &io_context) : io_context_(io_context) {}
37
45 auto track(const std::string &event_id,
46 std::function<void(const protocol::ok &)> callback,
47 std::chrono::milliseconds timeout) -> void
48 {
49 auto timer = std::make_shared<boost::asio::steady_timer>(*io_context_, timeout);
50
51 timer->async_wait([this, event_id](const boost::system::error_code &error) {
52 if (not error) { handle_timeout(event_id); }
53 });
54
55 pending_[event_id] = pending_request{ .callback =
56 [callback = std::move(callback)](const std::any &response_any) {
57 callback(std::any_cast<const protocol::ok &>(response_any));
58 },
59 .timer = timer };
60 }
61
68 [[nodiscard]] auto has_pending(const std::string &event_id) const -> bool { return pending_.contains(event_id); }
69
73 auto cancel_all_pending() -> void
74 {
75 for (auto &[event_id, request] : pending_) { request.timer->cancel(); }
76 pending_.clear();
77 }
78
86 template<typename ResponseType> auto resolve(const std::string &event_id, const ResponseType &response) -> void
87 {
88 auto iter = pending_.find(event_id);
89 if (iter != pending_.end()) {
90 iter->second.timer->cancel();
91 iter->second.callback(response);
92 pending_.erase(iter);
93 }
94 }
95
105 template<typename ResponseType = protocol::ok>
106 [[nodiscard]] auto async_track(std::string event_id, std::chrono::milliseconds timeout)
107 -> boost::asio::awaitable<ResponseType>
108 {
109 auto executor = co_await boost::asio::this_coro::executor;
110 boost::asio::cancellation_signal cancel_signal;
111 auto result = std::make_shared<ResponseType>();
112 auto event_done = std::make_shared<bool>(false);
113
114 static constexpr int hours_per_day = 24;
115 auto dummy_timer = std::make_shared<boost::asio::steady_timer>(*io_context_, std::chrono::hours(hours_per_day));
116 pending_[event_id] = pending_request{ [&cancel_signal, result, event_done](const std::any &response_any) {
117 *result = std::any_cast<ResponseType>(response_any);
118 *event_done = true;
119 cancel_signal.emit(boost::asio::cancellation_type::all);
120 },
121 dummy_timer };
122
123 boost::asio::steady_timer timeout_timer(executor, timeout);
124
125 boost::system::error_code error_code;
126 co_await timeout_timer.async_wait(boost::asio::bind_cancellation_slot(
127 cancel_signal.slot(), boost::asio::redirect_error(boost::asio::use_awaitable, error_code)));
128
129 pending_.erase(event_id);
130
131 if (error_code == boost::asio::error::operation_aborted and *event_done) { co_return *result; }
132
133 throw std::runtime_error("Request timeout");
134 }
135
136private:
142 auto handle_timeout(const std::string &event_id) -> void
143 {
144 auto iter = pending_.find(event_id);
145 if (iter != pending_.end()) {
146 protocol::ok timeout_response;
147 timeout_response.event_id = event_id;
148 timeout_response.accepted = false;
149 timeout_response.message = "Request timeout";
150
151 iter->second.callback(timeout_response);
152 pending_.erase(iter);
153 }
154 }
155
156 std::shared_ptr<boost::asio::io_context> io_context_;
157 std::unordered_map<std::string, pending_request> pending_;
158};
159
160}// namespace radix_relay::nostr
Tracks pending Nostr requests and matches them with responses.
auto resolve(const std::string &event_id, const ResponseType &response) -> void
Resolves a pending request with a response.
auto track(const std::string &event_id, std::function< void(const protocol::ok &)> callback, std::chrono::milliseconds timeout) -> void
Tracks a request with callback-based completion.
auto async_track(std::string event_id, std::chrono::milliseconds timeout) -> boost::asio::awaitable< ResponseType >
Tracks a request with coroutine-based completion.
auto has_pending(const std::string &event_id) const -> bool
Checks if an event ID has a pending request.
auto cancel_all_pending() -> void
Cancels all pending requests.
request_tracker(const std::shared_ptr< boost::asio::io_context > &io_context)
Constructs a request tracker.
Nostr OK response message.
Definition protocol.hpp:149
bool accepted
Whether the event was accepted.
Definition protocol.hpp:151
std::string message
Human-readable status message.
Definition protocol.hpp:152
std::string event_id
ID of the event this responds to.
Definition protocol.hpp:150