velocimeter/src/web.rs
2024-04-24 01:21:08 -07:00

178 lines
5.1 KiB
Rust

use std::fmt;
use futures_util::{SinkExt, StreamExt};
use http::Uri;
use serde::{Deserialize, Serialize};
use tokio::net::TcpStream;
use tokio_websockets::{MaybeTlsStream, Message, WebSocketStream};
use tracing::{debug, info, trace};
use crate::device::{Device, DeviceList};
const API_DOMAIN: &str = "doppler-transfer.com";
#[derive(Serialize, Deserialize, Debug)]
struct CodePayload {
code: String,
}
#[derive(Serialize, Deserialize, Debug)]
struct DevicePayload {
#[serde(rename = "type")]
device_type: String,
#[serde(rename = "device")]
device_id: String,
is_saved: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug)]
struct LanIpPayload {
url_lan: String,
push_token: Option<crate::device::Device>,
}
#[derive(Serialize, Deserialize, Debug)]
struct RequestDevicePayload {
code: String,
push_token: crate::device::SimplifiedDevice,
}
#[derive(Debug)]
pub enum Error {
Io(std::io::Error),
Websocket(tokio_websockets::Error),
Json(serde_json::Error),
NoData,
InvalidUri(http::uri::InvalidUri),
Http(reqwest::Error),
UnexpectedStatus(reqwest::StatusCode),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Io(err) => err.fmt(f),
Self::Websocket(err) => err.fmt(f),
Self::Json(err) => err.fmt(f),
Self::NoData => write!(f, "No data received from web server"),
Self::InvalidUri(err) => err.fmt(f),
Self::Http(err) => err.fmt(f),
Self::UnexpectedStatus(code) => write!(f, "Unexpected response: {}", code),
}
}
}
impl From<std::io::Error> for Error {
fn from(value: std::io::Error) -> Self {
Error::Io(value)
}
}
impl From<tokio_websockets::Error> for Error {
fn from(value: tokio_websockets::Error) -> Self {
Self::Websocket(value)
}
}
impl From<serde_json::Error> for Error {
fn from(value: serde_json::Error) -> Self {
Self::Json(value)
}
}
impl From<http::uri::InvalidUri> for Error {
fn from(value: http::uri::InvalidUri) -> Self {
Self::InvalidUri(value)
}
}
impl From<reqwest::Error> for Error {
fn from(value: reqwest::Error) -> Self {
Self::Http(value)
}
}
pub struct DopplerWebClient {
ws: WebSocketStream<MaybeTlsStream<TcpStream>>,
code: String,
}
impl DopplerWebClient {
pub fn code(&self) -> &str {
&self.code
}
pub async fn wait_for_device(mut self, list: &mut DeviceList) -> Result<(String, Uri), Error> {
let mut device: Option<DevicePayload> = None;
while let Some(msg) = self.ws.next().await {
if let Some(msg) = msg?.as_text() {
if let Some(ref device) = device {
let lan_ip: LanIpPayload = serde_json::from_str(msg)?;
trace!("Got LAN IP: {}", lan_ip.url_lan);
if let Some(ref token) = lan_ip.push_token {
info!("Device asked we save it");
list.add(token.clone());
}
return Ok((device.device_id.clone(), Uri::try_from(lan_ip.url_lan)?));
} else {
let mut payload: DevicePayload = serde_json::from_str(msg)?;
debug!("Got device: {:?}", &device);
payload.is_saved = Some(list.find_id(&payload.device_id).is_some());
let response = serde_json::to_string(&payload)?;
self.ws.send(Message::text(response)).await?;
device = Some(payload);
}
}
}
unreachable!();
}
pub async fn request_device(&mut self, device: &Device) -> Result<(), Error> {
let req = RequestDevicePayload {
code: self.code.clone(),
push_token: device.simplify(),
};
let api_url = http::Uri::builder()
.scheme("https")
.authority(API_DOMAIN)
.path_and_query("/api/v0/request-device".to_string())
.build()
.unwrap();
let response = reqwest::Client::new()
.post(api_url.to_string())
.json(&req)
.send()
.await?;
if response.status().as_u16() == 500 {
trace!("Got 500 (expected)");
Ok(())
} else {
Err(Error::UnexpectedStatus(response.status()))
}
}
}
pub async fn connect() -> Result<DopplerWebClient, Error> {
use tokio_websockets::ClientBuilder;
let doppler_url = http::Uri::builder()
.scheme("wss")
.authority(API_DOMAIN)
.path_and_query(format!("/api/v1/code?id={}", uuid::Uuid::new_v4()))
.build()
.unwrap();
let (mut client, _) = ClientBuilder::from_uri(doppler_url).connect().await?;
while let Some(next) = client.next().await {
let msg: Message = next?;
if msg.is_text() {
let code_data: CodePayload = serde_json::from_str(msg.as_text().unwrap())?;
return Ok(DopplerWebClient {
ws: client,
code: code_data.code,
});
}
}
Err(Error::NoData)
}