nakama_rs/
api_client.rs

1//! Stateful nakama client, abstracting over nakama api and rt_api.
2
3use crate::{
4    api::{
5        self, ApiLeaderboardRecordList, RestRequest,
6        WriteLeaderboardRecordRequestLeaderboardRecordWrite,
7    },
8    async_client::AsyncRequestTick,
9    rt_api::{Presence, Socket, SocketEvent},
10};
11
12use nanoserde::DeJson;
13use std::{cell::RefCell, collections::HashMap, rc::Rc};
14use crate::matchmaker::Matchmaker;
15
16pub enum Event {
17    Presence {
18        joins: Vec<Presence>,
19        leaves: Vec<Presence>,
20    },
21    MatchData {
22        data: Vec<u8>,
23        opcode: i32,
24        user_id: String,
25    },
26}
27
28pub struct NakamaState {
29    server_url: String,
30    ws_url: String,
31    port: u32,
32
33    pub socket: Option<Socket>,
34    pub username: Option<String>,
35    pub token: Option<String>,
36    pub refresh_token: Option<String>,
37    /// Stores the last received leaderboard record list for each leaderboard
38    pub leaderboards: HashMap<String, Rc<ApiLeaderboardRecordList>>,
39    pub match_id: Option<String>,
40    pub rpc_response: Option<String>,
41    pub error: Option<String>,
42    pub next_request: Option<Box<dyn AsyncRequestTick>>,
43}
44
45impl NakamaState {
46    pub fn reset(&mut self) {
47        self.socket = None;
48        self.username = None;
49        self.token = None;
50        self.refresh_token = None;
51        self.match_id = None;
52        self.error = None;
53    }
54
55    pub fn make_request<T, F>(&mut self, request: RestRequest<T>, on_success: F)
56    where
57        T: nanoserde::DeJson + 'static,
58        F: FnMut(T) -> () + 'static,
59    {
60        assert!(self.next_request.is_none());
61
62        let mut request = crate::async_client::make_request(&self.server_url, self.port, request);
63        request.on_success(on_success);
64        self.next_request = Some(Box::new(request));
65    }
66}
67
68/// Statefull, non-blocking nakama client.
69/// Works as a state machine - all calls are non-blocking, but may modify some
70/// internal ApiClient state and therefore results of other calls in the future.
71pub struct ApiClient {
72    key: String,
73    events: Vec<Event>,
74    pub session_id: Option<String>,
75    pub matchmaker_token: Option<String>,
76    state: Rc<RefCell<NakamaState>>,
77    ongoing_request: Option<Box<dyn AsyncRequestTick>>,
78    socket_response: HashMap<u32, SocketEvent>,
79}
80
81impl ApiClient {
82    pub fn new(key: &str, server: &str, port: u32, protocol: &str) -> ApiClient {
83        ApiClient {
84            key: key.to_owned(),
85            state: Rc::new(RefCell::new(NakamaState {
86                ws_url: match protocol {
87                    "http" => format!("ws://{}", server.to_owned()),
88                    "https" => format!("wss://{}", server.to_owned()),
89                    _ => panic!("Unsupported protocol"),
90                },
91
92                server_url: match protocol {
93                    "http" => format!("http://{}", server.to_owned()),
94                    "https" => format!("https://{}", server.to_owned()),
95                    _ => panic!("Unsupported protocol"),
96                },
97                port,
98                socket: None,
99                token: None,
100                refresh_token: None,
101                leaderboards: HashMap::new(),
102                rpc_response: None,
103                error: None,
104                username: None,
105                match_id: None,
106                next_request: None,
107            })),
108            socket_response: HashMap::new(),
109            ongoing_request: None,
110            events: vec![],
111            session_id: None,
112            matchmaker_token: None,
113        }
114    }
115
116    pub fn in_progress(&self) -> bool {
117        self.ongoing_request.is_some() || self.state.borrow().next_request.is_some()
118    }
119
120    pub fn authenticate(&mut self, email: &str, password: &str) {
121        self.session_id = None;
122        self.state.borrow_mut().socket = None;
123        self.state.borrow_mut().username = None;
124
125        let request = api::authenticate_email(
126            &self.key,
127            "",
128            api::ApiAccountEmail {
129                email: email.to_owned(),
130                password: password.to_owned(),
131                vars: std::collections::HashMap::new(),
132            },
133            Some(false),
134            None,
135        );
136
137        self.state.borrow_mut().make_request(request, {
138            let state2 = self.state.clone();
139            move |session| {
140                let mut state = state2.borrow_mut();
141                state.socket = Some(Socket::connect(
142                    &state.ws_url,
143                    state.port,
144                    false,
145                    &session.token,
146                ));
147                state.token = Some(session.token);
148                state.refresh_token = Some(session.refresh_token);
149
150                let request = api::get_account(&state.token.as_ref().unwrap());
151                state.make_request(request, {
152                    let state = state2.clone();
153                    move |account| {
154                        let mut state = state.borrow_mut();
155                        state.username = Some(account.user.username);
156                    }
157                });
158            }
159        });
160    }
161
162    pub fn register(&mut self, email: &str, password: &str, username: &str) {
163        let request = api::authenticate_email(
164            &self.key,
165            "",
166            api::ApiAccountEmail {
167                email: email.to_owned(),
168                password: password.to_owned(),
169                vars: std::collections::HashMap::new(),
170            },
171            Some(true),
172            Some(username),
173        );
174
175        self.state.borrow_mut().make_request(request, {
176            let state2 = self.state.clone();
177            move |session| {
178                let mut state = state2.borrow_mut();
179                state.socket = Some(Socket::connect(
180                    &state.ws_url,
181                    state.port,
182                    false,
183                    &session.token,
184                ));
185                state.token = Some(session.token);
186
187                let request = api::get_account(&state.token.as_ref().unwrap());
188                state.make_request(request, {
189                    let state = state2.clone();
190                    move |account| {
191                        let mut state = state.borrow_mut();
192                        state.username = Some(account.user.username);
193                    }
194                });
195            }
196        });
197    }
198
199    pub fn username(&self) -> Option<String> {
200        self.state.borrow().username.clone()
201    }
202
203    pub fn rpc(&mut self, name: &str, body: &str) {
204        self.state.borrow_mut().rpc_response = None;
205
206        let request = api::rpc_func(
207            &self.state.borrow().token.as_ref().unwrap(),
208            name,
209            body,
210            None,
211        );
212        self.state.borrow_mut().make_request(request, {
213            let state2 = self.state.clone();
214            move |response| {
215                state2.borrow_mut().rpc_response = Some(response.payload);
216            }
217        });
218    }
219
220    pub fn logout(&mut self) {
221        // let request = api::session_logout(
222        //     &self.state.borrow().token.as_ref().unwrap(),
223        //     api::ApiSessionLogoutRequest {
224        //         token: self.state.borrow().token.clone().unwrap(),
225        //         refresh_token: self.state.borrow().refresh_token.clone().unwrap(),
226        //     },
227        // );
228        // self.state.borrow_mut().make_request(request, |_| {});
229
230        // workaround: for some reasone nakama cant process logout request
231        // so we reset all nakama data to ensure that next time we will have a new connection
232        // but not really notifying the cloud that we want to switch an account
233        self.session_id = None;
234        self.matchmaker_token = None;
235        self.state.borrow_mut().reset();
236    }
237
238    pub fn authenticated(&self) -> bool {
239        self.state.borrow().username.is_some()
240            && self.state.borrow().socket.is_some()
241            && self.state.borrow().socket.as_ref().unwrap().connected()
242    }
243
244    pub fn write_leaderboard_record(&mut self, leaderboard_id: &str, score: i32) {
245        assert!(self.state.borrow().token.is_some());
246        let request = api::write_leaderboard_record(
247            &self.state.borrow().token.as_ref().unwrap(),
248            leaderboard_id,
249            WriteLeaderboardRecordRequestLeaderboardRecordWrite {
250                metadata: "".to_owned(),
251                subscore: "0".to_owned(),
252                score: score.to_string(),
253            },
254        );
255
256        self.state
257            .borrow_mut()
258            .make_request(request, |_response| {})
259    }
260
261    pub fn list_leaderboard_records(&mut self, leaderboard_id: &str) {
262        assert!(self.state.borrow().token.is_some());
263
264        let request = api::list_leaderboard_records(
265            &self.state.borrow().token.as_ref().unwrap(),
266            leaderboard_id,
267            &[],
268            // If there is no limit, only one entry is returned
269            Some(100),
270            None,
271            None,
272        );
273
274        let id = leaderboard_id.to_owned();
275        self.state.borrow_mut().make_request(request, {
276            let state2 = self.state.clone();
277            move |response| {
278                state2
279                    .borrow_mut()
280                    .leaderboards
281                    .insert(id.clone(), Rc::new(response));
282            }
283        })
284    }
285
286    pub fn leaderboard_records(
287        &self,
288        leaderboard_id: &str,
289    ) -> Option<Rc<ApiLeaderboardRecordList>> {
290        self.state
291            .borrow()
292            .leaderboards
293            .get(leaderboard_id)
294            .map(|records| records.clone())
295    }
296
297    pub fn try_recv(&mut self) -> Option<Event> {
298        self.events.pop()
299    }
300
301    pub fn tick(&mut self) {
302        let mut state = self.state.borrow_mut();
303        if let Some(ref mut socket) = state.socket {
304            if let Some(msg) = socket.try_recv() {
305                let event: SocketEvent = DeJson::deserialize_json(&msg).unwrap();
306
307                if let Some(ref cid) = event.cid {
308                    self.socket_response
309                        .insert(cid.parse::<u32>().unwrap(), event.clone());
310                }
311                if let Some(presence) = event.match_presence_event {
312                    self.events.push(Event::Presence {
313                        joins: presence.joins.iter().cloned().collect::<Vec<_>>(),
314                        leaves: presence.leaves.iter().cloned().collect::<Vec<_>>(),
315                    });
316                }
317
318                if let Some(new_match) = event.new_match {
319                    self.session_id = Some(new_match.self_user.session_id.clone());
320                    state.match_id = Some(new_match.match_id.clone());
321
322                    self.events.push(Event::Presence {
323                        joins: new_match.presences.clone(),
324                        leaves: vec![],
325                    });
326                }
327
328                if let Some(data) = event.match_data {
329                    self.events.push(Event::MatchData {
330                        user_id: data.presence.session_id,
331                        opcode: data.op_code.parse().unwrap(),
332                        data: data.data,
333                    });
334                }
335
336                if let Some(matched) = event.matchmaker_matched {
337                    self.matchmaker_token = Some(matched.token);
338                }
339            }
340        }
341        drop(state);
342
343        if let Some(ref mut request) = self.ongoing_request {
344            if request.tick() {
345                self.ongoing_request = None;
346            }
347        }
348
349        if let Some(request) = self.state.borrow_mut().next_request.take() {
350            assert!(self.ongoing_request.is_none());
351
352            self.ongoing_request = Some(request);
353        }
354    }
355
356    pub fn match_id(&self) -> Option<String> {
357        self.state.borrow().match_id.clone()
358    }
359
360    pub fn rpc_response(&self) -> Option<String> {
361        self.state.borrow().rpc_response.clone()
362    }
363
364    pub fn socket_add_matchmaker(
365        &mut self,
366        matchmaker: &Matchmaker,
367    ) {
368        let mut state = &mut *self.state.borrow_mut();
369
370        self.matchmaker_token = None;
371        state.match_id = None;
372
373        state.socket.as_mut().unwrap().add_matchmaker(
374            matchmaker.min_count,
375            matchmaker.max_count,
376            matchmaker.query.as_str(),
377            matchmaker.string_properties().as_str(),
378            matchmaker.numeric_properties().as_str(),
379        );
380    }
381
382    pub fn socket_create_match(&mut self) -> u32 {
383        self.state
384            .borrow_mut()
385            .socket
386            .as_mut()
387            .unwrap()
388            .create_match()
389    }
390
391    pub fn socket_join_match_by_id(&mut self, match_id: &str) -> u32 {
392        self.state
393            .borrow_mut()
394            .socket
395            .as_mut()
396            .unwrap()
397            .join_match_by_id(match_id)
398    }
399
400    pub fn socket_join_match_by_token(&mut self, token_id: &str) -> u32 {
401        self.state
402            .borrow_mut()
403            .socket
404            .as_mut()
405            .unwrap()
406            .join_match_by_token(token_id)
407    }
408    pub fn socket_leave_match(&mut self) -> u32 {
409        let state = &mut *self.state.borrow_mut();
410
411        state
412            .socket
413            .as_mut()
414            .unwrap()
415            .leave_match(state.match_id.as_ref().unwrap())
416    }
417
418    pub fn socket_send<T: nanoserde::SerBin>(&mut self, opcode: i32, data: &T) {
419        let binary_data = nanoserde::SerBin::serialize_bin(data);
420
421        let state = &mut *self.state.borrow_mut();
422
423        state.socket.as_mut().unwrap().match_data_send(
424            state.match_id.as_ref().unwrap(),
425            opcode,
426            &binary_data,
427        );
428    }
429
430    pub fn socket_response(&self, cid: u32) -> Option<SocketEvent> {
431        self.socket_response.get(&cid).cloned()
432    }
433
434    pub fn error(&self) -> Option<String> {
435        self.state.borrow().error.clone()
436    }
437}