feat: implement client

This commit is contained in:
2026-02-12 13:29:15 -08:00
parent 0d0a548a46
commit 5aa6b98742
23 changed files with 645 additions and 164 deletions

View File

@@ -1,3 +0,0 @@
pub mod peers;
pub mod register;
pub mod ws;

View File

@@ -1,8 +0,0 @@
use actix_web::{HttpResponse, web};
use crate::{AppState, error::Result, storage::StorageImpl};
pub async fn get_peers(app_state: web::Data<AppState>) -> Result<HttpResponse> {
let peers = app_state.storage.get_peers().await?;
Ok(HttpResponse::Ok().json(peers))
}

View File

@@ -1,27 +0,0 @@
use crate::{
AppState, PeerUpdate,
error::Result,
storage::{RegisterRequest, StorageImpl},
utils::Peer,
};
use actix_web::{HttpResponse, web};
pub async fn register_peer(
app_state: web::Data<AppState>,
request: web::Json<RegisterRequest>,
) -> Result<HttpResponse> {
app_state.storage.register_device(&request).await?;
app_state
.peer_updates
.send(PeerUpdate {
peer: Peer {
public_key: request.public_key.as_str().to_string(),
public_ip: request.public_ip.to_string(),
port: request.port.clone(),
allowed_ips: request.allowed_ips.clone(),
},
})
.unwrap();
Ok(HttpResponse::Ok().finish())
}

View File

@@ -1 +0,0 @@
pub mod peers;

View File

@@ -1,88 +1,88 @@
use crate::{AppState, error::Error, storage::StorageImpl};
use actix_web::{HttpRequest, HttpResponse, rt, web};
use actix_ws::AggregatedMessage;
use futures_util::StreamExt;
use tokio::sync::broadcast::error::RecvError;
pub async fn peers(
req: HttpRequest,
stream: web::Payload,
app_state: web::Data<AppState>,
) -> Result<HttpResponse, Error> {
let (res, mut session, msg_stream) =
actix_ws::handle(&req, stream).map_err(|e| Error::ws(e))?;
let mut msg_stream = msg_stream.aggregate_continuations();
let mut peer_rx = app_state.peer_updates.subscribe();
match app_state.storage.get_peers().await {
Ok(initial_peers) => {
let json = serde_json::to_string(&initial_peers).unwrap_or_else(|_| "[]".to_string());
if session.text(json).await.is_err() {
return Ok(res);
}
tracing::info!(
"sent initial peer list ({} peers) to new client",
initial_peers.len()
);
}
Err(e) => {
tracing::warn!("failed to fetch initial peers: {:?}", e);
session.close(None).await.ok();
return Ok(res);
}
}
rt::spawn(async move {
loop {
tokio::select! {
msg = msg_stream.next() => {
match msg {
Some(Ok(AggregatedMessage::Ping(data))) => {
if session.pong(&data).await.is_err() {
break;
}
}
Some(Ok(AggregatedMessage::Pong(_))) => {}
Some(Ok(AggregatedMessage::Close(_))) => {
break;
}
Some(Ok(AggregatedMessage::Text(_))) => {
}
Some(Ok(AggregatedMessage::Binary(_))) => {
}
Some(Err(_)) => {
break;
}
None => {
break;
}
}
}
update = peer_rx.recv() => {
match update {
Ok(peer_update) => {
let json = serde_json::to_string(&peer_update.peer)
.unwrap_or_else(|_| "{}".to_string());
if session.text(json).await.is_err() {
break;
}
tracing::info!("sent peer update to client: {}", peer_update.peer.public_key);
}
Err(RecvError::Lagged(n)) => {
tracing::warn!("client lagged, missed {} updates", n);
}
Err(RecvError::Closed) => {
break;
}
}
}
}
}
session.close(None).await.ok();
tracing::info!("client disconnected");
});
Ok(res)
}
use crate::{AppState, error::Error, storage::StorageImpl};
use actix_web::{HttpRequest, HttpResponse, rt, web};
use actix_ws::AggregatedMessage;
use futures_util::StreamExt;
+use tokio::sync::broadcast::error::RecvError;
pub async fn peers(
req: HttpRequest,
stream: web::Payload,
app_state: web::Data<AppState>,
) -> Result<HttpResponse, Error> {
let (res, mut session, msg_stream) =
actix_ws::handle(&req, stream).map_err(|e| Error::ws(e))?;
let mut msg_stream = msg_stream.aggregate_continuations();
let mut peer_rx = app_state.peer_updates.subscribe();
match app_state.storage.get_peers().await {
Ok(initial_peers) => {
let json = serde_json::to_string(&initial_peers).unwrap_or_else(|_| "[]".to_string());
if session.text(json).await.is_err() {
return Ok(res);
}
tracing::info!(
+ "sent initial peer list ({} peers) to new client",
initial_peers.len()
);
}
Err(e) => {
tracing::warn!("failed to fetch initial peers: {:?}", e);
+ session.close(None).await.ok();
return Ok(res);
}
}
rt::spawn(async move {
loop {
tokio::select! {
msg = msg_stream.next() => {
match msg {
Some(Ok(AggregatedMessage::Ping(data))) => {
if session.pong(&data).await.is_err() {
break;
}
}
Some(Ok(AggregatedMessage::Pong(_))) => {}
Some(Ok(AggregatedMessage::Close(_))) => {
break;
}
Some(Ok(AggregatedMessage::Text(_))) => {
}
Some(Ok(AggregatedMessage::Binary(_))) => {
}
Some(Err(_)) => {
break;
}
None => {
break;
}
}
}
update = peer_rx.recv() => {
match update {
Ok(peer_update) => {
let json = serde_json::to_string(&peer_update.peer)
.unwrap_or_else(|_| "{}".to_string());
if session.text(json).await.is_err() {
break;
}
+ tracing::info!("sent peer update to client: {}", peer_update.peer.public_key);
}
+ Err(RecvError::Lagged(n)) => {
+ tracing::warn!("client lagged, missed {} updates", n);
}
+ Err(RecvError::Closed) => {
break;
}
}
}
}
}
+ session.close(None).await.ok();
+ tracing::info!("client disconnected");
});
Ok(res)
}