178 lines
5.1 KiB
Rust
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)
|
|
}
|