Radix Relay
Hybrid mesh communications with Signal Protocol encryption
Loading...
Searching...
No Matches
command_handler.hpp
Go to the documentation of this file.
1#pragma once
2
5#include <core/events.hpp>
6#include <core/overload.hpp>
7#include <fmt/core.h>
8#include <memory>
10
11#include "internal_use_only/config.hpp"
12
14
15template<concepts::signal_bridge Bridge> struct command_handler_context
16{
17 using display_queue_t = std::shared_ptr<async::async_queue<events::display_filter_input_t>>;
18 using transport_queue_t = std::shared_ptr<async::async_queue<events::transport::in_t>>;
19 using session_queue_t = std::shared_ptr<async::async_queue<events::session_orchestrator::in_t>>;
20 using connection_monitor_queue_t = std::shared_ptr<async::async_queue<events::connection_monitor::in_t>>;
21
22 std::shared_ptr<Bridge> bridge;
27
28 template<typename... Args> auto emit(fmt::format_string<Args...> format_string, Args &&...args) const -> void
29 {
30 display_queue->push(events::display_message{ .message = fmt::format(format_string, std::forward<Args>(args)...),
31 .contact_rdx = std::nullopt,
32 .timestamp = platform::current_timestamp_ms(),
34 }
35};
36
37template<concepts::signal_bridge Bridge>
38auto make_command_handler(std::shared_ptr<Bridge> bridge,
39 std::shared_ptr<async::async_queue<events::display_filter_input_t>> display_queue,
40 std::shared_ptr<async::async_queue<events::transport::in_t>> transport_queue,
41 std::shared_ptr<async::async_queue<events::session_orchestrator::in_t>> session_queue,
42 std::shared_ptr<async::async_queue<events::connection_monitor::in_t>> connection_monitor_queue)
43{
44 auto ctx = std::make_shared<command_handler_context<Bridge>>(command_handler_context<Bridge>{
45 .bridge = std::move(bridge),
46 .display_queue = std::move(display_queue),
47 .transport_queue = std::move(transport_queue),
48 .session_queue = std::move(session_queue),
49 .connection_monitor_queue = std::move(connection_monitor_queue),
50 });
51
52 return overload{
53 [ctx](const events::help &) {
54 ctx->emit(
55 "Interactive Commands:\n"
56 " /broadcast <message> Send to all local peers\n"
57 " /chat <contact> Enter chat mode with contact\n"
58 " /connect <relay> Add Nostr relay\n"
59 " /disconnect Disconnect from Nostr relay\n"
60 " /identities List discovered identities\n"
61 " /leave Exit chat mode\n"
62 " /mode <internet|mesh|hybrid> Switch transport mode\n"
63 " /peers List discovered peers\n"
64 " /publish Publish identity to network\n"
65 " /scan Force peer discovery\n"
66 " /send <peer> <message> Send encrypted message to peer\n"
67 " /sessions Show encrypted sessions\n"
68 " /status Show network status\n"
69 " /trust <peer> [alias] Establish session with peer\n"
70 " /verify <peer> Show safety numbers\n"
71 " /version Show version information\n"
72 " /quit Exit interactive mode\n");
73 },
74
75 [ctx](const events::peers &) {
76 ctx->emit(
77 "Connected Peers: (transport layer not implemented)\n"
78 " No peers discovered yet\n");
79 },
80
81 [ctx](const events::status &) {
82 ctx->connection_monitor_queue->push(events::connection_monitor::query_status{});
83 std::string node_fingerprint = ctx->bridge->get_node_fingerprint();
84 ctx->emit("\nCrypto Status:\n Node Fingerprint: {}\n", node_fingerprint);
85 },
86
87 [ctx](const events::sessions &) {
88 auto contacts = ctx->bridge->list_contacts();
89
90 if (contacts.empty()) {
91 ctx->emit("No active sessions\n");
92 return;
93 }
94
95 ctx->emit("Active Sessions ({}):\n", contacts.size());
96 for (const auto &contact : contacts) {
97 if (contact.user_alias.empty()) {
98 ctx->emit(" {}\n", contact.rdx_fingerprint);
99 } else {
100 ctx->emit(" {} ({})\n", contact.user_alias, contact.rdx_fingerprint);
101 }
102 }
103 },
104
105 [ctx](const events::identities &) { ctx->session_queue->push(events::list_identities{}); },
106
107 [ctx](const events::publish_identity &) {
108 ctx->session_queue->push(events::publish_identity{});
109 ctx->emit("Publishing identity to network...\n");
110 },
111
112 [ctx](const events::unpublish_identity &) {
113 ctx->session_queue->push(events::unpublish_identity{});
114 ctx->emit("Unpublishing identity from network...\n");
115 },
116
117 [ctx](const events::scan &) {
118 ctx->emit(
119 "Scanning for BLE peers... (BLE transport not implemented)\n"
120 " No peers found\n");
121 },
122
123 [ctx](const events::version &) { ctx->emit("Radix Relay v{}\n", radix_relay::cmake::project_version); },
124
125 [ctx](const events::mode &command) {
126 if (command.new_mode == "internet" or command.new_mode == "mesh" or command.new_mode == "hybrid") {
127 ctx->emit("Switched to {} mode\n", command.new_mode);
128 } else {
129 ctx->emit("Invalid mode. Use: internet, mesh, or hybrid\n");
130 }
131 },
132
133 [ctx](const events::send &command) {
134 if (not command.peer.empty() and not command.message.empty()) {
135 ctx->session_queue->push(command);
136 ctx->emit("Sending '{}' to '{}'...\n", command.message, command.peer);
137 } else {
138 ctx->emit("Usage: send <peer> <message>\n");
139 }
140 },
141
142 [ctx](const events::broadcast &command) {
143 if (not command.message.empty()) {
144 ctx->emit("Broadcasting '{}' to all local peers (not implemented)\n", command.message);
145 } else {
146 ctx->emit("Usage: broadcast <message>\n");
147 }
148 },
149
150 [ctx](const events::connect &command) {
151 if (not command.relay.empty()) {
152 ctx->session_queue->push(command);
153 ctx->emit("Connecting to Nostr relay {}\n", command.relay);
154 } else {
155 ctx->emit("Usage: connect <relay>\n");
156 }
157 },
158
159 [ctx](const events::disconnect &) {
160 ctx->transport_queue->push(events::transport::disconnect{});
161 ctx->emit("Disconnecting from Nostr relay\n");
162 },
163
164 [ctx](const events::trust &command) {
165 if (not command.peer.empty()) {
166 ctx->session_queue->push(command);
167 ctx->emit("Establishing session with {}...\n", command.peer);
168 } else {
169 ctx->emit("Usage: trust <peer> [alias]\n");
170 }
171 },
172
173 [ctx](const events::verify &command) {
174 if (not command.peer.empty()) {
175 ctx->emit("Safety numbers for {} (Signal Protocol not implemented)\n", command.peer);
176 } else {
177 ctx->emit("Usage: verify <peer>\n");
178 }
179 },
180
181 [ctx](const events::chat &command) {
182 if (command.contact.empty()) {
183 ctx->emit("Usage: /chat <contact>\n");
184 return;
185 }
186
187 try {
188 const auto contact = ctx->bridge->lookup_contact(command.contact);
189 const auto display_name = contact.user_alias.empty() ? contact.rdx_fingerprint : contact.user_alias;
190
191 ctx->display_queue->push(
192 events::enter_chat_mode{ .rdx_fingerprint = contact.rdx_fingerprint, .display_name = display_name });
193
194 constexpr std::uint32_t history_limit = 5;
195 const auto messages = ctx->bridge->get_conversation_messages(contact.rdx_fingerprint, history_limit, 0);
196
197 if (not messages.empty()) {
198 ctx->display_queue->push(events::display_message{
199 .message = fmt::format("--- Conversation History ({} messages) ---", messages.size()),
200 .contact_rdx = contact.rdx_fingerprint,
201 .timestamp = platform::current_timestamp_ms(),
203
204 for (auto it = messages.rbegin(); it != messages.rend(); ++it) {
205 const auto &msg = *it;
206
207 const auto direction_indicator = (msg.direction == signal::MessageDirection::Incoming) ? "← " : "→ ";
208 const auto sender_name = (msg.direction == signal::MessageDirection::Incoming) ? display_name : "You";
209
210 const auto formatted_message = fmt::format("{}{}: {}", direction_indicator, sender_name, msg.content);
211
212 const auto source_type = (msg.direction == signal::MessageDirection::Incoming)
215
216 ctx->display_queue->push(events::display_message{ .message = formatted_message,
217 .contact_rdx = contact.rdx_fingerprint,
218 .timestamp = msg.timestamp,
219 .source_type = source_type });
220 }
221
222 ctx->display_queue->push(events::display_message{ .message = "--- End of History ---",
223 .contact_rdx = contact.rdx_fingerprint,
224 .timestamp = platform::current_timestamp_ms(),
226
227 const auto newest_timestamp = messages.front().timestamp;
228 ctx->bridge->mark_conversation_read_up_to(contact.rdx_fingerprint, newest_timestamp);
229 } else {
230 ctx->bridge->mark_conversation_read(contact.rdx_fingerprint);
231 }
232
233 ctx->emit("Entering chat with {}\n", display_name);
234 } catch (const std::exception &) {
235 ctx->emit("Contact not found: {}\n", command.contact);
236 }
237 },
238
239 [ctx](const events::leave &) {
240 ctx->display_queue->push(events::exit_chat_mode{});
241 ctx->emit("Exiting chat mode\n");
242 },
243
244 [](const events::unknown_command & /*command*/) {
245 // No-op: unknown commands are silently ignored
246 },
247 };
248}
249
250template<concepts::signal_bridge Bridge>
251using command_handler = decltype(make_command_handler(std::declval<std::shared_ptr<Bridge>>(),
252 std::declval<std::shared_ptr<async::async_queue<events::display_filter_input_t>>>(),
253 std::declval<std::shared_ptr<async::async_queue<events::transport::in_t>>>(),
254 std::declval<std::shared_ptr<async::async_queue<events::session_orchestrator::in_t>>>(),
255 std::declval<std::shared_ptr<async::async_queue<events::connection_monitor::in_t>>>()));
256
257}// namespace radix_relay::core
Thread-safe asynchronous queue for message passing between coroutines.
auto make_command_handler(std::shared_ptr< Bridge > bridge, std::shared_ptr< async::async_queue< events::display_filter_input_t > > display_queue, std::shared_ptr< async::async_queue< events::transport::in_t > > transport_queue, std::shared_ptr< async::async_queue< events::session_orchestrator::in_t > > session_queue, std::shared_ptr< async::async_queue< events::connection_monitor::in_t > > connection_monitor_queue)
decltype(make_command_handler(std::declval< std::shared_ptr< Bridge > >(), std::declval< std::shared_ptr< async::async_queue< events::display_filter_input_t > > >(), std::declval< std::shared_ptr< async::async_queue< events::transport::in_t > > >(), std::declval< std::shared_ptr< async::async_queue< events::session_orchestrator::in_t > > >(), std::declval< std::shared_ptr< async::async_queue< events::connection_monitor::in_t > > >())) command_handler
auto current_timestamp_ms() -> std::uint64_t
Gets current timestamp in milliseconds since Unix epoch.
std::shared_ptr< async::async_queue< events::connection_monitor::in_t > > connection_monitor_queue_t
std::shared_ptr< async::async_queue< events::session_orchestrator::in_t > > session_queue_t
auto emit(fmt::format_string< Args... > format_string, Args &&...args) const -> void
connection_monitor_queue_t connection_monitor_queue
std::shared_ptr< async::async_queue< events::transport::in_t > > transport_queue_t
std::shared_ptr< async::async_queue< events::display_filter_input_t > > display_queue_t
Broadcast message to all peers.
Definition events.hpp:63
Enter chat mode with a specific contact.
Definition events.hpp:103
Disconnect from current relay.
Definition events.hpp:75
Request to display a message to the user.
Definition events.hpp:381
@ incoming_message
Received message from contact.
std::string message
Message content to display.
Definition events.hpp:391
Enter chat mode with specified contact.
Definition events.hpp:399
std::string rdx_fingerprint
Contact to chat with.
Definition events.hpp:400
Exit chat mode, return to showing all messages.
Definition events.hpp:406
Request display of available commands.
Definition events.hpp:15
Request list of discovered identities.
Definition events.hpp:35
Request list of all discovered identities.
Definition events.hpp:187
Change operational mode.
Definition events.hpp:50
Request list of connected peers.
Definition events.hpp:20
Publish identity bundle to the network.
Definition events.hpp:80
Request scan for nearby peers.
Definition events.hpp:40
Send encrypted message to a specific peer.
Definition events.hpp:56
Request list of active sessions.
Definition events.hpp:30
Request current system status.
Definition events.hpp:25
Command to disconnect from transport.
Definition events.hpp:274
Establish trust with a peer and assign an alias.
Definition events.hpp:90
Unknown or unrecognized command.
Definition events.hpp:114
Remove identity bundle from the network.
Definition events.hpp:85
Verify identity fingerprint of a peer.
Definition events.hpp:97
Request application version information.
Definition events.hpp:45
Helper for std::visit with overload pattern.
Definition overload.hpp:17