Having trouble?
@@ -121,6 +126,7 @@
diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
index 65da817..c321c4c 100644
--- a/src-tauri/Cargo.lock
+++ b/src-tauri/Cargo.lock
@@ -345,6 +345,22 @@ dependencies = [
"syn 2.0.101",
]
+[[package]]
+name = "async-tungstenite"
+version = "0.29.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef0f7efedeac57d9b26170f72965ecfd31473ca52ca7a64e925b0b6f5f079886"
+dependencies = [
+ "atomic-waker",
+ "futures-core",
+ "futures-io",
+ "futures-task",
+ "futures-util",
+ "log",
+ "pin-project-lite",
+ "tungstenite",
+]
+
[[package]]
name = "atk"
version = "0.18.2"
@@ -1280,6 +1296,7 @@ dependencies = [
"droplet-rs",
"dynfmt",
"filetime",
+ "futures-lite",
"gethostname",
"hex 0.4.3",
"http 1.3.1",
@@ -1296,6 +1313,7 @@ dependencies = [
"reqwest 0.12.16",
"reqwest-middleware 0.4.2",
"reqwest-middleware-cache",
+ "reqwest-websocket",
"rustbreak",
"rustix 0.38.44",
"schemars",
@@ -4357,6 +4375,24 @@ dependencies = [
"url",
]
+[[package]]
+name = "reqwest-websocket"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f91a811daaa8b54faeaec9d507a336897a3d243834a4965254a17d39da8b5c9"
+dependencies = [
+ "async-tungstenite",
+ "bytes",
+ "futures-util",
+ "reqwest 0.12.16",
+ "thiserror 2.0.12",
+ "tokio",
+ "tokio-util",
+ "tracing",
+ "tungstenite",
+ "web-sys",
+]
+
[[package]]
name = "rfd"
version = "0.15.3"
@@ -5831,6 +5867,7 @@ checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df"
dependencies = [
"bytes",
"futures-core",
+ "futures-io",
"futures-sink",
"pin-project-lite",
"tokio",
@@ -6009,6 +6046,23 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
+[[package]]
+name = "tungstenite"
+version = "0.26.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13"
+dependencies = [
+ "bytes",
+ "data-encoding",
+ "http 1.3.1",
+ "httparse",
+ "log",
+ "rand 0.9.1",
+ "sha1",
+ "thiserror 2.0.12",
+ "utf-8",
+]
+
[[package]]
name = "typeid"
version = "1.0.3"
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index fe8ae93..c0909e7 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -68,6 +68,8 @@ known-folders = "1.2.0"
native_model = { version = "0.6.1", features = ["rmp_serde_1_3"] }
tauri-plugin-opener = "2.4.0"
bitcode = "0.6.6"
+reqwest-websocket = "0.5.0"
+futures-lite = "2.6.0"
# tailscale = { path = "./tailscale" }
[dependencies.dynfmt]
diff --git a/src-tauri/src/error/remote_access_error.rs b/src-tauri/src/error/remote_access_error.rs
index 1d60937..fdc4b0c 100644
--- a/src-tauri/src/error/remote_access_error.rs
+++ b/src-tauri/src/error/remote_access_error.rs
@@ -13,6 +13,7 @@ use super::drop_server_error::DropServerError;
#[derive(Debug, SerializeDisplay)]
pub enum RemoteAccessError {
FetchError(Arc
),
+ FetchErrorWS(Arc),
ParsingError(ParseError),
InvalidEndpoint,
HandshakeFailed(String),
@@ -29,7 +30,10 @@ impl Display for RemoteAccessError {
match self {
RemoteAccessError::FetchError(error) => {
if error.is_connect() {
- return write!(f, "Failed to connect to Drop server. Check if you access Drop through a browser, and then try again.");
+ return write!(
+ f,
+ "Failed to connect to Drop server. Check if you access Drop through a browser, and then try again."
+ );
}
write!(
@@ -42,20 +46,40 @@ impl Display for RemoteAccessError {
.or_else(|| Some("Unknown error".to_string()))
.unwrap()
)
- },
+ }
+ RemoteAccessError::FetchErrorWS(error) => write!(
+ f,
+ "{}: {}",
+ error,
+ error
+ .source()
+ .map(|e| e.to_string())
+ .or_else(|| Some("Unknown error".to_string()))
+ .unwrap()
+ ),
RemoteAccessError::ParsingError(parse_error) => {
write!(f, "{parse_error}")
}
RemoteAccessError::InvalidEndpoint => write!(f, "invalid drop endpoint"),
- RemoteAccessError::HandshakeFailed(message) => write!(f, "failed to complete handshake: {message}"),
+ RemoteAccessError::HandshakeFailed(message) => {
+ write!(f, "failed to complete handshake: {message}")
+ }
RemoteAccessError::GameNotFound(id) => write!(f, "could not find game on server: {id}"),
- RemoteAccessError::InvalidResponse(error) => write!(f, "server returned an invalid response: {}, {}", error.status_code, error.status_message),
- RemoteAccessError::UnparseableResponse(error) => write!(f, "server returned an invalid response: {error}"),
- RemoteAccessError::ManifestDownloadFailed(status, response) => write!(
+ RemoteAccessError::InvalidResponse(error) => write!(
f,
- "failed to download game manifest: {status} {response}"
+ "server returned an invalid response: {}, {}",
+ error.status_code, error.status_message
+ ),
+ RemoteAccessError::UnparseableResponse(error) => {
+ write!(f, "server returned an invalid response: {error}")
+ }
+ RemoteAccessError::ManifestDownloadFailed(status, response) => {
+ write!(f, "failed to download game manifest: {status} {response}")
+ }
+ RemoteAccessError::OutOfSync => write!(
+ f,
+ "server's and client's time are out of sync. Please ensure they are within at least 30 seconds of each other"
),
- RemoteAccessError::OutOfSync => write!(f, "server's and client's time are out of sync. Please ensure they are within at least 30 seconds of each other"),
RemoteAccessError::Cache(error) => write!(f, "Cache Error: {error}"),
}
}
@@ -66,6 +90,11 @@ impl From for RemoteAccessError {
RemoteAccessError::FetchError(Arc::new(err))
}
}
+impl From for RemoteAccessError {
+ fn from(err: reqwest_websocket::Error) -> Self {
+ RemoteAccessError::FetchErrorWS(Arc::new(err))
+ }
+}
impl From for RemoteAccessError {
fn from(err: ParseError) -> Self {
RemoteAccessError::ParsingError(err)
diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs
index 0049dfb..13f6525 100644
--- a/src-tauri/src/lib.rs
+++ b/src-tauri/src/lib.rs
@@ -12,6 +12,7 @@ mod process;
mod remote;
use crate::process::commands::open_process_logs;
+use crate::remote::commands::auth_initiate_code;
use crate::{database::db::DatabaseImpls, games::downloads::commands::resume_download};
use bitcode::{Decode, Encode};
use client::commands::fetch_state;
@@ -267,6 +268,7 @@ pub fn run() {
fetch_settings,
// Auth
auth_initiate,
+ auth_initiate_code,
retry_connect,
manual_recieve_handshake,
sign_out,
diff --git a/src-tauri/src/remote/auth.rs b/src-tauri/src/remote/auth.rs
index 3f4b63b..712f323 100644
--- a/src-tauri/src/remote/auth.rs
+++ b/src-tauri/src/remote/auth.rs
@@ -9,12 +9,12 @@ use tauri::{AppHandle, Emitter, Manager};
use url::Url;
use crate::{
+ AppState, AppStatus, User,
database::{
db::{borrow_db_checked, borrow_db_mut_checked},
models::data::DatabaseAuth,
},
error::{drop_server_error::DropServerError, remote_access_error::RemoteAccessError},
- AppState, AppStatus, User,
};
use super::{
@@ -32,6 +32,7 @@ struct InitiateRequestBody {
name: String,
platform: String,
capabilities: HashMap,
+ mode: String,
}
#[derive(Serialize)]
@@ -166,7 +167,7 @@ pub fn recieve_handshake(app: AppHandle, path: String) {
app.emit("auth/finished", ()).unwrap();
}
-pub fn auth_initiate_logic() -> Result<(), RemoteAccessError> {
+pub fn auth_initiate_logic(mode: String) -> Result {
let base_url = {
let db_lock = borrow_db_checked();
Url::parse(&db_lock.base_url.clone())?
@@ -182,6 +183,7 @@ pub fn auth_initiate_logic() -> Result<(), RemoteAccessError> {
("peerAPI".to_owned(), CapabilityConfiguration {}),
("cloudSaves".to_owned(), CapabilityConfiguration {}),
]),
+ mode,
};
let client = reqwest::blocking::Client::new();
@@ -194,13 +196,9 @@ pub fn auth_initiate_logic() -> Result<(), RemoteAccessError> {
return Err(RemoteAccessError::HandshakeFailed(data.status_message));
}
- let redir_url = response.text()?;
- let complete_redir_url = base_url.join(&redir_url)?;
+ let response = response.text()?;
- debug!("opening web browser to continue authentication");
- webbrowser::open(complete_redir_url.as_ref()).unwrap();
-
- Ok(())
+ Ok(response)
}
pub fn setup() -> (AppStatus, Option) {
diff --git a/src-tauri/src/remote/commands.rs b/src-tauri/src/remote/commands.rs
index 81462ef..8b4e98e 100644
--- a/src-tauri/src/remote/commands.rs
+++ b/src-tauri/src/remote/commands.rs
@@ -1,15 +1,18 @@
use std::sync::Mutex;
-use log::debug;
+use futures_lite::StreamExt;
+use log::{debug, warn};
use reqwest::blocking::Client;
+use reqwest_websocket::{Message, RequestBuilderExt};
+use serde::Deserialize;
use tauri::{AppHandle, Emitter, Manager};
use url::Url;
use crate::{
+ AppState, AppStatus,
database::db::{borrow_db_checked, borrow_db_mut_checked},
error::remote_access_error::RemoteAccessError,
remote::{auth::generate_authorization_header, requests::make_request},
- AppState, AppStatus,
};
use super::{
@@ -91,7 +94,78 @@ pub fn retry_connect(state: tauri::State<'_, Mutex>) {
#[tauri::command]
pub fn auth_initiate() -> Result<(), RemoteAccessError> {
- auth_initiate_logic()
+ let base_url = {
+ let db_lock = borrow_db_checked();
+ Url::parse(&db_lock.base_url.clone())?
+ };
+
+ let redir_url = auth_initiate_logic("callback".to_string())?;
+ let complete_redir_url = base_url.join(&redir_url)?;
+
+ debug!("opening web browser to continue authentication");
+ webbrowser::open(complete_redir_url.as_ref()).unwrap();
+ Ok(())
+}
+
+#[derive(Deserialize)]
+struct CodeWebsocketResponse {
+ #[serde(rename = "type")]
+ response_type: String,
+ value: String,
+}
+
+#[tauri::command]
+pub fn auth_initiate_code(app: AppHandle) -> Result {
+ let base_url = {
+ let db_lock = borrow_db_checked();
+ Url::parse(&db_lock.base_url.clone())?
+ };
+
+ let code = auth_initiate_logic("code".to_string())?;
+ let header_code = code.clone();
+
+ tauri::async_runtime::spawn(async move {
+ let load = async || -> Result<(), RemoteAccessError> {
+ let ws_url = base_url.join("/api/v1/client/auth/code/ws")?;
+ let response = reqwest::Client::default()
+ .get(ws_url)
+ .header("Authorization", header_code)
+ .upgrade()
+ .send()
+ .await?;
+
+ let mut websocket = response.into_websocket().await?;
+
+ while let Some(token) = websocket.try_next().await? {
+ if let Message::Text(response) = token {
+ let response = serde_json::from_str::(&response)
+ .map_err(|e| RemoteAccessError::UnparseableResponse(e.to_string()))?;
+ match response.response_type.as_str() {
+ "token" => {
+ let recieve_app = app.clone();
+ tauri::async_runtime::spawn_blocking(move || {
+ manual_recieve_handshake(recieve_app, response.value);
+ });
+ return Ok(());
+ }
+ _ => return Err(RemoteAccessError::HandshakeFailed(response.value)),
+ }
+ }
+ }
+
+ Err(RemoteAccessError::HandshakeFailed(
+ "Failed to connect to websocket".to_string(),
+ ))
+ };
+
+ let result = load().await;
+ if let Err(err) = result {
+ warn!("{err}");
+ app.emit("auth/failed", err.to_string()).unwrap();
+ }
+ });
+
+ Ok(code)
}
#[tauri::command]