Skip to main content

nil_client/client/
mod.rs

1// Copyright (C) Call of Nil contributors
2// SPDX-License-Identifier: AGPL-3.0-only
3
4#![expect(clippy::wildcard_imports)]
5
6mod auth;
7mod battle;
8mod chat;
9mod cheat;
10mod city;
11mod continent;
12mod infrastructure;
13mod military;
14mod npc;
15mod player;
16mod ranking;
17mod report;
18mod round;
19mod user;
20mod world;
21
22use crate::error::{Error, Result};
23use crate::http::authorization::Authorization;
24use crate::http::circuit_breaker::CircuitBreaker;
25use crate::http::retry::Retry;
26use crate::http::{self, USER_AGENT};
27use crate::server::ServerAddr;
28use crate::websocket::WebSocketClient;
29use futures::future::BoxFuture;
30use local_ip_address::local_ip;
31use nil_core::event::Event;
32use nil_core::player::PlayerId;
33use nil_core::world::config::WorldId;
34use nil_crypto::password::Password;
35use nil_payload::AuthorizeRequest;
36use nil_payload::world::LeaveRequest;
37use nil_server_types::ServerKind;
38use nil_server_types::auth::Token;
39use std::borrow::Cow;
40use std::net::{IpAddr, SocketAddrV4};
41use std::sync::nonpoison::Mutex;
42use std::sync::{Arc, Weak};
43
44pub struct Client {
45  server: ServerAddr,
46  world_id: Option<WorldId>,
47  authorization: Option<Authorization>,
48  websocket: Option<WebSocketClient>,
49  circuit_breaker: Arc<Mutex<CircuitBreaker>>,
50  retry: Retry,
51  user_agent: Cow<'static, str>,
52}
53
54#[bon::bon]
55impl Client {
56  #[inline]
57  pub fn new(server: ServerAddr) -> Self {
58    Self {
59      server,
60      world_id: None,
61      authorization: None,
62      websocket: None,
63      circuit_breaker: Arc::new(Mutex::default()),
64      retry: Retry::with_attempts(2),
65      user_agent: Cow::Borrowed(USER_AGENT),
66    }
67  }
68
69  #[inline]
70  pub fn new_local(addr: SocketAddrV4) -> Self {
71    Self::new(ServerAddr::Local { addr })
72  }
73
74  #[inline]
75  pub fn new_remote() -> Self {
76    Self::new(ServerAddr::Remote)
77  }
78
79  #[builder]
80  pub async fn update<OnEvent>(
81    &mut self,
82    #[builder(start_fn)] server: ServerAddr,
83    world_id: Option<WorldId>,
84    world_password: Option<Password>,
85    player_id: Option<PlayerId>,
86    player_password: Option<Password>,
87    authorization_token: Option<Token>,
88    on_event: Option<OnEvent>,
89  ) -> Result<()>
90  where
91    OnEvent: Fn(Event) -> BoxFuture<'static, ()> + Send + Sync + 'static,
92  {
93    self.stop().await;
94    self.world_id = world_id;
95
96    if server != self.server {
97      self.server = server;
98      self
99        .circuit_breaker
100        .set(CircuitBreaker::new());
101    }
102
103    if self.server.is_remote()
104      && let Some(token) = authorization_token
105      && let Some(id) = self.validate_token(&token).await?
106      && player_id.as_ref().is_none_or(|it| it == &id)
107      && let Ok(authorization) = Authorization::new(token)
108    {
109      self.authorization = Some(authorization);
110    } else if let Some(player) = player_id {
111      let req = AuthorizeRequest { player, password: player_password };
112      self.authorization = self
113        .authorize(req)
114        .await
115        .map(|token| Some(Authorization::new(&token)))?
116        .transpose()
117        .inspect_err(|err| tracing::error!(message = %err, error = ?err))
118        .map_err(|_| Error::FailedToAuthenticate)?;
119    }
120
121    if self.world_id.is_none()
122      && self.server.is_local()
123      && let ServerKind::Local { id } = self.get_server_kind().await?
124    {
125      self.world_id = Some(id);
126    }
127
128    if let Some(world_id) = self.world_id
129      && let Some(on_event) = on_event
130      && let Some(authorization) = self.authorization.clone()
131    {
132      let websocket = WebSocketClient::connect(server)
133        .world_id(world_id)
134        .maybe_world_password(world_password)
135        .authorization(authorization)
136        .user_agent(&self.user_agent)
137        .on_event(on_event)
138        .call()
139        .await?;
140
141      self.websocket = Some(websocket);
142    }
143
144    Ok(())
145  }
146
147  pub async fn stop(&mut self) {
148    if let Some(world) = self.world_id
149      && self.authorization.is_some()
150    {
151      let req = LeaveRequest { world };
152      if let Err(err) = self.leave(req).await {
153        tracing::error!(message = %err, error = ?err);
154      }
155    }
156
157    self.server = ServerAddr::Remote;
158    self.world_id = None;
159    self.authorization = None;
160    self.websocket = None;
161  }
162
163  pub fn server_addr(&self) -> ServerAddr {
164    let mut addr = self.server;
165    if let ServerAddr::Local { addr } = &mut addr
166      && addr.ip().is_loopback()
167      && let Ok(ip) = local_ip()
168      && let IpAddr::V4(ip) = ip
169    {
170      addr.set_ip(ip);
171    }
172
173    addr
174  }
175
176  #[inline]
177  pub fn server(&self) -> ServerAddr {
178    self.server
179  }
180
181  #[inline]
182  pub fn world(&self) -> Option<WorldId> {
183    self.world_id
184  }
185
186  #[inline]
187  pub fn user_agent(&self) -> &str {
188    &self.user_agent
189  }
190
191  pub fn set_user_agent(&mut self, user_agent: &str) {
192    self.user_agent = Cow::Owned(user_agent.to_owned());
193  }
194
195  #[inline]
196  pub fn is_local(&self) -> bool {
197    self.server.is_local()
198  }
199
200  #[inline]
201  pub fn is_remote(&self) -> bool {
202    self.server.is_remote()
203  }
204
205  fn circuit_breaker(&self) -> Weak<Mutex<CircuitBreaker>> {
206    Arc::downgrade(&self.circuit_breaker)
207  }
208
209  pub async fn get_server_kind(&self) -> Result<ServerKind> {
210    http::json_get("get-server-kind")
211      .server(self.server)
212      .retry(&self.retry)
213      .circuit_breaker(self.circuit_breaker())
214      .user_agent(&self.user_agent)
215      .send()
216      .await
217  }
218
219  pub async fn get_server_version(&self) -> Result<String> {
220    http::get_text("version")
221      .server(self.server)
222      .retry(&self.retry)
223      .circuit_breaker(self.circuit_breaker())
224      .user_agent(&self.user_agent)
225      .send()
226      .await
227  }
228
229  pub async fn is_ready(&self) -> bool {
230    http::get("")
231      .server(self.server)
232      .retry(&self.retry)
233      .circuit_breaker(self.circuit_breaker())
234      .user_agent(&self.user_agent)
235      .send()
236      .await
237      .map(|()| true)
238      .unwrap_or(false)
239  }
240}
241
242impl Default for Client {
243  fn default() -> Self {
244    Self::new_remote()
245  }
246}