1#![expect(clippy::wildcard_imports)]
5
6mod auth;
7mod battle;
8mod capital;
9mod chat;
10mod cheat;
11mod city;
12mod continent;
13mod infrastructure;
14mod market;
15mod military;
16mod npc;
17mod player;
18mod ranking;
19mod report;
20mod round;
21mod user;
22mod world;
23
24use crate::authorization::Authorization;
25use crate::circuit_breaker::CircuitBreaker;
26use crate::error::{Error, Result};
27use crate::http::{self, USER_AGENT};
28use crate::retry::Retry;
29use crate::server::ServerAddr;
30use crate::websocket::WebSocketClient;
31use bon::Builder;
32use futures::future::BoxFuture;
33use local_ip_address::local_ip;
34use nil_core::event::Event;
35use nil_core::player::PlayerId;
36use nil_core::world::config::WorldId;
37use nil_crypto::password::Password;
38use nil_payload::request::auth::AuthorizeRequest;
39use nil_payload::request::world::LeaveRequest;
40use nil_payload::response::server::*;
41use nil_server_types::ServerKind;
42use nil_server_types::auth::Token;
43use serde::{Deserialize, Serialize};
44use std::borrow::Cow;
45use std::net::{IpAddr, SocketAddrV4};
46use std::sync::nonpoison::Mutex;
47use std::sync::{Arc, Weak};
48
49pub struct Client {
50 server: ServerAddr,
51 world_id: Option<WorldId>,
52 authorization: Option<Authorization>,
53 websocket: Option<WebSocketClient>,
54 circuit_breaker: Arc<Mutex<CircuitBreaker>>,
55 retry: Retry,
56 user_agent: Cow<'static, str>,
57}
58
59impl Client {
60 #[inline]
61 pub fn new(server: ServerAddr) -> Self {
62 Self {
63 server,
64 world_id: None,
65 authorization: None,
66 websocket: None,
67 circuit_breaker: Arc::new(Mutex::default()),
68 retry: Retry::with_attempts(2),
69 user_agent: Cow::Borrowed(USER_AGENT),
70 }
71 }
72
73 #[inline]
74 pub fn new_local(addr: SocketAddrV4) -> Self {
75 Self::new(ServerAddr::Local { addr })
76 }
77
78 #[inline]
79 pub fn new_remote() -> Self {
80 Self::new(ServerAddr::Remote)
81 }
82
83 pub async fn update<OnEvent>(
84 &mut self,
85 options: ClientOptions,
86 on_event: Option<OnEvent>,
87 ) -> Result<()>
88 where
89 OnEvent: Fn(Event) -> BoxFuture<'static, ()> + Send + Sync + 'static,
90 {
91 let update = Update {
92 client: self,
93 server: options.server,
94 world_id: options.world_id,
95 world_password: options.world_password,
96 player_id: options.player_id,
97 player_password: options.player_password,
98 authorization_token: options.authorization_token,
99 on_event,
100 };
101
102 update.execute().await
103 }
104
105 pub async fn stop(&mut self) {
106 if let Some(world) = self.world_id
107 && self.authorization.is_some()
108 {
109 let req = LeaveRequest { world };
110 if let Err(err) = self.leave(req).await {
111 tracing::error!(message = %err, error = ?err);
112 }
113 }
114
115 self.world_id = None;
116 self.authorization = None;
117 self.websocket = None;
118 }
119
120 pub fn server_addr(&self) -> ServerAddr {
121 let mut addr = self.server;
122 if let ServerAddr::Local { addr } = &mut addr
123 && addr.ip().is_loopback()
124 && let Ok(ip) = local_ip()
125 && let IpAddr::V4(ip) = ip
126 {
127 addr.set_ip(ip);
128 }
129
130 addr
131 }
132
133 #[inline]
134 pub fn world(&self) -> Option<WorldId> {
135 self.world_id
136 }
137
138 #[inline]
139 pub fn user_agent(&self) -> &str {
140 &self.user_agent
141 }
142
143 #[inline]
144 pub fn set_user_agent(&mut self, user_agent: &str) {
145 self.user_agent = Cow::Owned(user_agent.to_owned());
146 }
147
148 #[inline]
149 pub fn is_local(&self) -> bool {
150 self.server.is_local()
151 }
152
153 #[inline]
154 pub fn is_remote(&self) -> bool {
155 self.server.is_remote()
156 }
157
158 fn circuit_breaker(&self) -> Weak<Mutex<CircuitBreaker>> {
159 Arc::downgrade(&self.circuit_breaker)
160 }
161
162 pub async fn get_server_kind(&self) -> Result<GetServerKindResponse> {
164 http::json_get("get-server-kind")
165 .server(self.server)
166 .retry(&self.retry)
167 .circuit_breaker(self.circuit_breaker())
168 .user_agent(&self.user_agent)
169 .send()
170 .await
171 }
172
173 pub async fn is_ready(&self) -> bool {
175 http::get("")
176 .server(self.server)
177 .retry(&self.retry)
178 .circuit_breaker(self.circuit_breaker())
179 .user_agent(&self.user_agent)
180 .send()
181 .await
182 .map(|()| true)
183 .unwrap_or_else(|err| {
184 tracing::error!(message = %err, error = ?err);
185 false
186 })
187 }
188
189 pub async fn version(&self) -> Result<VersionResponse> {
191 http::json_get("version")
192 .server(self.server)
193 .retry(&self.retry)
194 .circuit_breaker(self.circuit_breaker())
195 .user_agent(&self.user_agent)
196 .send()
197 .await
198 }
199}
200
201impl Default for Client {
202 fn default() -> Self {
203 Self::new_remote()
204 }
205}
206
207#[derive(Builder, Clone, Debug, Deserialize, Serialize)]
208#[serde(rename_all = "camelCase")]
209#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
210#[cfg_attr(feature = "typescript", ts(export, optional_fields = nullable))]
211pub struct ClientOptions {
212 #[builder(start_fn, into)]
213 pub server: ServerAddr,
214
215 #[serde(default)]
216 #[builder(into)]
217 pub world_id: Option<WorldId>,
218
219 #[serde(default)]
220 #[builder(into)]
221 pub world_password: Option<Password>,
222
223 #[serde(default)]
224 #[builder(into)]
225 pub player_id: Option<PlayerId>,
226
227 #[serde(default)]
228 #[builder(into)]
229 pub player_password: Option<Password>,
230
231 #[serde(default)]
232 #[builder(into)]
233 pub authorization_token: Option<Token>,
234}
235
236struct Update<'a, OnEvent>
237where
238 OnEvent: Fn(Event) -> BoxFuture<'static, ()> + Send + Sync + 'static,
239{
240 client: &'a mut Client,
241 server: ServerAddr,
242 world_id: Option<WorldId>,
243 world_password: Option<Password>,
244 player_id: Option<PlayerId>,
245 player_password: Option<Password>,
246 authorization_token: Option<Token>,
247 on_event: Option<OnEvent>,
248}
249
250impl<OnEvent> Update<'_, OnEvent>
251where
252 OnEvent: Fn(Event) -> BoxFuture<'static, ()> + Send + Sync + 'static,
253{
254 async fn execute(self) -> Result<()> {
255 self.client.stop().await;
256 self.client.world_id = self.world_id;
257
258 if self.server != self.client.server {
259 self.client.server = self.server;
260 self
261 .client
262 .circuit_breaker
263 .set(CircuitBreaker::new());
264 }
265
266 if self.client.server.is_remote()
267 && let Some(token) = self.authorization_token
268 && let Some(id) = self
269 .client
270 .validate_token((&token).into())
271 .await?
272 .0
273 && self
274 .player_id
275 .as_ref()
276 .is_none_or(|it| it == &id)
277 && let Ok(authorization) = Authorization::new(token)
278 {
279 self.client.authorization = Some(authorization);
280 } else if let Some(player) = self.player_id {
281 let req = AuthorizeRequest {
282 player,
283 password: self.player_password,
284 };
285
286 self.client.authorization = self
287 .client
288 .authorize(req)
289 .await
290 .map(|token| Some(Authorization::new(&token.0)))?
291 .transpose()
292 .inspect_err(|err| tracing::error!(message = %err, error = ?err))
293 .map_err(|_| Error::FailedToAuthenticate)?;
294 }
295
296 if self.client.world_id.is_none()
297 && self.client.server.is_local()
298 && let ServerKind::Local { id } = self.client.get_server_kind().await?.0
299 {
300 self.client.world_id = Some(id);
301 }
302
303 if let Some(world_id) = self.client.world_id
304 && let Some(on_event) = self.on_event
305 && let Some(authorization) = self.client.authorization.clone()
306 {
307 let websocket = WebSocketClient::connect(self.client.server)
308 .world_id(world_id)
309 .maybe_world_password(self.world_password)
310 .authorization(authorization)
311 .circuit_breaker(self.client.circuit_breaker())
312 .user_agent(&self.client.user_agent)
313 .on_event(on_event)
314 .call()
315 .await?;
316
317 self.client.websocket = Some(websocket);
318 }
319
320 Ok(())
321 }
322}