diff --git a/Cargo.toml b/Cargo.toml index 1504a361..f40830bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,9 @@ [workspace] resolver = "2" -members = [ "src-tauri", ] +members = [ + "src-tauri", + "core" +] [workspace.package] license = "AGPL-3.0-only" @@ -21,7 +24,7 @@ uuid = { version = "1.8", features = [ "serde" ] } tokio = { version = "1", features = [ "full" ] } serde = { version = "1", features = [ "derive" ] } chrono = { version = "0.4", features = [ "serde" ] } - +reqwest = { version = "0.12", features = [ "json" ] } serde_json = { version = "1" } flate2 = { version = "1" } tar = { version = "0.4" } @@ -29,6 +32,8 @@ zip = { version = "0.6" } async-trait = { version = "0.1" } thiserror = { version = "1" } anyhow = { version = "1" } +dirs = { version = "5" } +open = { version = "5" } [profile.dev] # Make compilation faster on macOS diff --git a/core/Cargo.toml b/core/Cargo.toml new file mode 100644 index 00000000..6a389ad2 --- /dev/null +++ b/core/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "launcher-core" +version = "0.0.0" +description = "The core for our launcher" +license = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } +documentation = { workspace = true } +readme = { workspace = true } +homepage = { workspace = true } +authors = { workspace = true } + +[dependencies] +tokio = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +flate2 = { workspace = true } +tar = { workspace = true } +zip = { workspace = true } +uuid = { workspace = true } +async-trait = { workspace = true } +thiserror = { workspace = true } +anyhow = { workspace = true } +chrono = { workspace = true } +reqwest = { workspace = true } +dirs = { workspace = true } +open = { workspace = true } diff --git a/src-tauri/src/auth/microsoft_auth.rs b/core/src/auth/microsoft_auth.rs similarity index 92% rename from src-tauri/src/auth/microsoft_auth.rs rename to core/src/auth/microsoft_auth.rs index c4e6cc7a..1d5ae16d 100644 --- a/src-tauri/src/auth/microsoft_auth.rs +++ b/core/src/auth/microsoft_auth.rs @@ -4,11 +4,9 @@ use std::{ }; use serde_json::json; -use tauri::{AppHandle, Runtime}; -use tauri_plugin_shell::ShellExt; use thiserror::Error; -use crate::{constants::CLIENT_ID, utils::http::create_client, PolyError, PolyResult}; +use crate::{constants::{self, CLIENT_ID}, utils::http::create_client, PolyError, PolyResult}; use super::{Account, AuthenticationError, AuthenticationMethod}; @@ -37,14 +35,14 @@ pub enum MicrosoftAuthError { UnknownError(u64), } -pub(super) struct MicrosoftAuthenticationMethod; +pub struct MicrosoftAuthenticationMethod; impl AuthenticationMethod for MicrosoftAuthenticationMethod { - async fn auth(handle: &AppHandle, stage: F) -> PolyResult + async fn auth(stage: F) -> PolyResult where F: Fn(String, u8, bool) -> (), { stage("Authenticating with Microsoft".into(), 0, false); - let msa_code: String = msa_code(handle).await?; + let msa_code: String = msa_code().await?; stage("Authenticating with Microsoft".into(), 1, false); let msa_token: String = msa_code_to_token(msa_code).await?; @@ -196,7 +194,7 @@ async fn msa_code_to_token(code: String) -> PolyResult { .post("https://login.live.com/oauth20_token.srf") .form(&[ ("client_id", CLIENT_ID), - ("redirect_uri", &format!("http://localhost:{}/", PORT)), + ("redirect_uri", &format!("http://localhost:{}/", constants::MSA_PORT)), ("code", &code), ("grant_type", "authorization_code"), ]) @@ -208,20 +206,19 @@ async fn msa_code_to_token(code: String) -> PolyResult { Ok(token["access_token"].as_str().unwrap().to_string()) } -const PORT: u16 = 13523; -async fn msa_code(handle: &AppHandle) -> PolyResult { +async fn msa_code() -> PolyResult { let url = format!( "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?client_id={}&response_type=code&redirect_uri=http://localhost:{}/&response_mode=query&scope=XboxLive.signin%20offline_access&prompt=consent", CLIENT_ID, - PORT + constants::MSA_PORT ); - handle.shell().open(url, None)?; + open::that(url)?; let mut token: String = String::new(); - let listener = TcpListener::bind(SocketAddr::from(([127, 0, 0, 1], PORT)))?; - println!("Started local server for MSA on port {}", PORT); + let listener = TcpListener::bind(SocketAddr::from(([127, 0, 0, 1], constants::MSA_PORT)))?; + println!("Started local server for MSA on port {}", constants::MSA_PORT); for conn in listener.incoming() { match conn { diff --git a/src-tauri/src/auth/mod.rs b/core/src/auth/mod.rs similarity index 60% rename from src-tauri/src/auth/mod.rs rename to core/src/auth/mod.rs index b2c72e28..c18e1efc 100644 --- a/src-tauri/src/auth/mod.rs +++ b/core/src/auth/mod.rs @@ -1,14 +1,10 @@ use anyhow::anyhow; use serde::{Deserialize, Serialize}; -use tauri::{plugin::TauriPlugin, AppHandle, Manager, Runtime}; -use tauri_plugin_http::reqwest::Client; use thiserror::Error; -use crate::{PolyError, PolyResult}; +use crate::{utils::http, PolyError, PolyResult}; -use self::microsoft_auth::MicrosoftAuthenticationMethod; - -mod microsoft_auth; +pub mod microsoft_auth; #[derive(Error, Debug)] pub enum AuthenticationError { @@ -35,12 +31,12 @@ pub struct AccountSkin { pub trait AuthenticationMethod { /// Authenticate with a given method. Stage is a function that takes a string and a u8. /// The string is the message to display to the user, and the u8 is the progress of the authentication. - async fn auth(handle: &AppHandle, stage: F) -> PolyResult + async fn auth(stage: F) -> PolyResult where F: Fn(String, u8, bool) -> (); async fn get_profile(access_token: String) -> PolyResult { - let response = Client::new() + let response = http::create_client()? .get("https://api.minecraftservices.com/minecraft/profile") .header("Authorization", format!("Bearer {}", access_token)) .send() @@ -55,22 +51,3 @@ pub trait AuthenticationMethod { Ok(account) } } - -// Plugin for MSA -pub fn init() -> TauriPlugin { - tauri::plugin::Builder::new("auth") - .invoke_handler(tauri::generate_handler![login_msa]) - .build() -} - -#[tauri::command] -async fn login_msa(app: tauri::AppHandle) -> Result { - match MicrosoftAuthenticationMethod::auth(&app, |status, stage, was_last| { - let _ = app.emit("msa:status", (status, stage, was_last)); - }) - .await - { - Ok(account) => Ok(account), - Err(err) => Err(err.to_string()), - } -} diff --git a/core/src/constants.rs b/core/src/constants.rs new file mode 100644 index 00000000..4c67c0b3 --- /dev/null +++ b/core/src/constants.rs @@ -0,0 +1,12 @@ +// Authentication and HTTP (TODO: env?) +pub const APP_CONFIG_DIR: &str = "org.polyfrost.onelauncher"; +pub const USER_AGENT: &str = "OneLauncher/1.0.0 (polyfrost.org)"; + +// Microsoft Authentication +pub const CLIENT_ID: &str = "9419b7ee-1448-4d1b-b52a-550d8f36ab56"; +pub const MSA_PORT: u16 = 13523; + +// Minecraft +pub const MINECRAFT_VERSIONS_MANIFEST: &str = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json"; + +// Java Downloading \ No newline at end of file diff --git a/src-tauri/src/game/client.rs b/core/src/game/client.rs similarity index 78% rename from src-tauri/src/game/client.rs rename to core/src/game/client.rs index 0b629bb1..5099fc02 100644 --- a/src-tauri/src/game/client.rs +++ b/core/src/game/client.rs @@ -4,10 +4,9 @@ use anyhow::anyhow; use async_trait::async_trait; use chrono::{DateTime, Local}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use tauri::{AppHandle, Manager}; use uuid::Uuid; -use crate::PolyResult; +use crate::{utils::dirs, PolyResult}; use super::{minecraft::{MinecraftManifest, ReleaseType}, vanilla::{self, VanillaClientProps}}; @@ -54,13 +53,13 @@ impl ClientType { #[async_trait] pub trait ClientTrait<'a>: Send + Sync { - fn get_impl(handle: AppHandle, client: &'a ClientType, instance: &'a Instance, manifest: &'a Manifest) -> Box + 'a> where Self: Sized { + fn get_impl(client: &'a ClientType, instance: &'a Instance, manifest: &'a Manifest) -> Box + 'a> where Self: Sized { Box::new(match client { - ClientType::Vanilla(_) => vanilla::VanillaClient::new(handle, instance, manifest) + ClientType::Vanilla(_) => vanilla::VanillaClient::new(instance, manifest) }) } - fn new(handle: AppHandle, instance: &'a Instance, manifest: &'a Manifest) -> Self where Self: Sized; + fn new(instance: &'a Instance, manifest: &'a Manifest) -> Self where Self: Sized; async fn launch(&self) -> PolyResult<()>; async fn setup(&self) -> PolyResult<()>; @@ -72,17 +71,6 @@ pub trait ClientTrait<'a>: Send + Sync { fn get_instance(&self) -> &'a Instance where Self: Sized; fn get_manifest(&self) -> &'a Manifest where Self: Sized; - fn get_handle(&'a self) -> &'a AppHandle where Self: Sized; -} - -#[macro_export] -macro_rules! create_game_client_props { - ($client:ident { $($name:ident: $type:ty),* }) => { - #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] - pub struct $client { - $(pub $name: $type),* - } - }; } #[macro_export] @@ -95,7 +83,6 @@ macro_rules! create_game_client { #[derive(Debug, Clone)] pub struct $client<'a> { - pub handle: tauri::AppHandle, pub instance: &'a crate::game::client::Instance, pub manifest: &'a crate::game::client::Manifest, $(pub $name: $type),* @@ -113,36 +100,27 @@ macro_rules! impl_game_client { fn get_manifest(&self) -> &'a crate::game::client::Manifest where Self: Sized { self.manifest } - - fn get_handle(&'a self) -> &'a tauri::AppHandle where Self: Sized { - &self.handle - } }; } pub struct ClientManager { - handle: AppHandle, instances: HashMap, - instances_dir: PathBuf, - manifests: HashMap, - manifests_dir: PathBuf, } impl ClientManager { - pub fn new(handle: &AppHandle) -> PolyResult { - let instances_dir = handle.path().app_config_dir()?.join("instances"); - let instances = load_and_serialize::(&instances_dir)?; + pub fn new() -> PolyResult { + let instances = load_and_serialize::( + &dirs::instances_dir()? + )?; - let manifests_dir = handle.path().app_config_dir()?.join("manifests"); - let manifests = load_and_serialize::(&manifests_dir)?; + let manifests = load_and_serialize::( + &dirs::manifests_dir()? + )?; Ok(Self { - handle: handle.clone(), instances, - instances_dir, manifests, - manifests_dir, }) } @@ -177,7 +155,7 @@ impl ClientManager { group: Option, client: ClientType ) -> PolyResult { - let version_cache = self.handle.path().app_config_dir()?.join("versions_cache.json"); + let version_cache = dirs::app_config_dir()?.join("versions_cache.json"); let versions = client.get_versions(Some(&version_cache)).await?; let url = &versions.iter().find(|v| v.id == version).ok_or(anyhow!("Version not found"))?.url; @@ -190,7 +168,7 @@ impl ClientManager { manifest: minecraft_manifest }; - save(&self.manifests_dir.join(format!("{}.json", uuid)), &manifest)?; + save(&dirs::manifests_dir()?.join(format!("{}.json", uuid)), &manifest)?; self.manifests.insert(uuid, manifest); // Save the instance @@ -203,7 +181,7 @@ impl ClientManager { client }; - save(&self.instances_dir.join(format!("{}.json", uuid)), &instance)?; + save(&dirs::instances_dir()?.join(format!("{}.json", uuid)), &instance)?; self.instances.insert(uuid, instance); Ok(uuid) diff --git a/src-tauri/src/game/java.rs b/core/src/game/java.rs similarity index 100% rename from src-tauri/src/game/java.rs rename to core/src/game/java.rs diff --git a/src-tauri/src/game/minecraft.rs b/core/src/game/minecraft.rs similarity index 100% rename from src-tauri/src/game/minecraft.rs rename to core/src/game/minecraft.rs diff --git a/src-tauri/src/game/mod.rs b/core/src/game/mod.rs similarity index 58% rename from src-tauri/src/game/mod.rs rename to core/src/game/mod.rs index 98d40578..be79bbce 100644 --- a/src-tauri/src/game/mod.rs +++ b/core/src/game/mod.rs @@ -1,5 +1,4 @@ pub mod client; pub mod java; pub mod vanilla; -pub mod plugin; pub mod minecraft; \ No newline at end of file diff --git a/src-tauri/src/game/vanilla.rs b/core/src/game/vanilla.rs similarity index 96% rename from src-tauri/src/game/vanilla.rs rename to core/src/game/vanilla.rs index a682c66d..9a0a3f40 100644 --- a/src-tauri/src/game/vanilla.rs +++ b/core/src/game/vanilla.rs @@ -15,9 +15,8 @@ create_game_client! { impl<'a> ClientTrait<'a> for VanillaClient<'a> { impl_game_client!(); - fn new(handle: tauri::AppHandle, instance: &'a Instance, manifest: &'a Manifest) -> Self { + fn new(instance: &'a Instance, manifest: &'a Manifest) -> Self { VanillaClient { - handle, instance, manifest } diff --git a/core/src/lib.rs b/core/src/lib.rs new file mode 100644 index 00000000..635e359f --- /dev/null +++ b/core/src/lib.rs @@ -0,0 +1,65 @@ +use thiserror::Error; + +pub mod constants; +pub mod utils; +pub mod game; +pub mod auth; + +/// A standardized [`Error`] type that is used across the launcher +#[derive(Debug, Error)] +pub enum PolyError { + /// Wrapper around [`tokio::sync::AcquireError`] to handle sempahore errors. + #[error("failed to manage a tokio semaphore: {0}")] + TokioError(#[from] tokio::sync::AcquireError), + + /// Wrapper around [`flate2::CompressError`] to handle flate compression errors. + #[error("failed to compress a file with flate: {0}")] + FlateError(#[from] flate2::CompressError), + + /// Wrapper around [`flate2::DecompressError`] to handle flate decompression errors. + #[error("failed to decompress a file with flate: {0}")] + DeflateError(#[from] flate2::DecompressError), + + /// Wrapper around [`uuid::Error`] to handle UUID parsing errors. + #[error("failed to parse uuids: {0}")] + UUIDError(#[from] uuid::Error), + + /// Wrapper around [`serde_json::Error`] to handle Serde JSON parsing errors. + #[error("failed to manage serialization of json: {0}")] + SerdeError(#[from] serde_json::Error), + + /// Wrapper around [`std::io::Error`] only to be used if a more defined error isn't available (e.g. when using `zip`) + #[error("failed to manage writing and reading of files: {0}")] + IOError(#[from] std::io::Error), + + /// Wrapper around [`reqwest::Error`] to handle HTTP errors. + #[error("failed to establish a HTTP connection: {0}")] + HTTPError(#[from] reqwest::Error), + + /// Wrapper around [`zip::result::ZipError`] to handle zip errors. + #[error("failed to manage zip files: {0}")] + ZipError(#[from] zip::result::ZipError), + + /// Wrapper around [`anyhow::Error`] to handle generic errors. + #[error(transparent)] + AnyhowError(#[from] anyhow::Error), + + /// Wrapper around [`chrono::ParseError`] to handle date parsing errors. + #[error("failed to parse a date: {0}")] + ChronoError(#[from] chrono::ParseError), + + /// Wrapper around [`game::java::JavaDownloadError`] to handle Java executable downloading. + #[error("failed to download java: {0}")] + JavaError(#[from] game::java::JavaDownloadError), + + /// Wrapper around [`auth::AuthenticationError`] to handle authentication errors. + #[error("failed to authenticate: {0}")] + AuthError(#[from] auth::AuthenticationError), + + /// Wrapper around [`utils::dirs::DirectoryError`] to handle directory errors. + #[error("failed to get directory: {0}")] + DirectoryError(#[from] utils::dirs::DirectoryError), +} + +/// Alias for a [`Result`] with the error type [`PolyError`]. +pub type PolyResult = anyhow::Result; \ No newline at end of file diff --git a/core/src/utils/dirs.rs b/core/src/utils/dirs.rs new file mode 100644 index 00000000..3a4efbc2 --- /dev/null +++ b/core/src/utils/dirs.rs @@ -0,0 +1,35 @@ +use std::path::PathBuf; + +use thiserror::Error; + +use crate::{constants, PolyResult}; + +#[derive(Debug, Error)] +pub enum DirectoryError { + #[error("failed to get the user's home directory")] + HomeDir, + #[error("failed to get the user's config directory")] + ConfigDir, +} + +pub fn app_config_dir() -> PolyResult { + dirs::config_dir() + .and_then(|dir| Some(dir.join(constants::APP_CONFIG_DIR))) + .ok_or(DirectoryError::ConfigDir.into()) +} + +pub fn libraries_dir() -> PolyResult { + Ok(app_config_dir()?.join("libraries")) +} + +pub fn java_dir() -> PolyResult { + Ok(app_config_dir()?.join("java")) +} + +pub fn instances_dir() -> PolyResult { + Ok(app_config_dir()?.join("instances")) +} + +pub fn manifests_dir() -> PolyResult { + Ok(app_config_dir()?.join("manifests")) +} \ No newline at end of file diff --git a/src-tauri/src/utils/file.rs b/core/src/utils/file.rs similarity index 100% rename from src-tauri/src/utils/file.rs rename to core/src/utils/file.rs diff --git a/src-tauri/src/utils/http.rs b/core/src/utils/http.rs similarity index 93% rename from src-tauri/src/utils/http.rs rename to core/src/utils/http.rs index 89066a25..a834f094 100644 --- a/src-tauri/src/utils/http.rs +++ b/core/src/utils/http.rs @@ -4,7 +4,7 @@ use std::{ path::Path, }; -use tauri_plugin_http::reqwest::{Client, ClientBuilder}; +use reqwest::{Client, ClientBuilder}; use crate::{constants::USER_AGENT, PolyError, PolyResult}; diff --git a/src-tauri/src/utils/mod.rs b/core/src/utils/mod.rs similarity index 68% rename from src-tauri/src/utils/mod.rs rename to core/src/utils/mod.rs index ac34b973..5980c9dc 100644 --- a/src-tauri/src/utils/mod.rs +++ b/core/src/utils/mod.rs @@ -1,2 +1,3 @@ pub mod file; pub mod http; +pub mod dirs; \ No newline at end of file diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 997218af..a24056cd 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -14,21 +14,18 @@ authors = { workspace = true } tauri-build = { workspace = true } [dependencies] +launcher-core = { path = "../core" } tauri = { workspace = true } tauri-plugin-shell = { workspace = true } tauri-plugin-http = { workspace = true } tokio = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -flate2 = { workspace = true } -tar = { workspace = true } -zip = { workspace = true } -uuid = { workspace = true } async-trait = { workspace = true } thiserror = { workspace = true } anyhow = { workspace = true } chrono = { workspace = true } -sha1 = "0.10.6" +uuid = { workspace = true } [features] # This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!! diff --git a/src-tauri/build.rs b/src-tauri/build.rs index c9450726..ec058383 100644 --- a/src-tauri/build.rs +++ b/src-tauri/build.rs @@ -3,13 +3,13 @@ use tauri_build::{Attributes, InlinedPlugin}; fn main() { let _ = tauri_build::try_build( Attributes::new() - .plugin("auth", InlinedPlugin::new().commands(&["login_msa"])) .plugin( - "game", + "launcher-core", InlinedPlugin::new().commands(&[ + "login_msa", "create_instance", "get_instances", - "get_instance" + "get_instance", ]), ), ); diff --git a/src-tauri/capabilities/migrated.json b/src-tauri/capabilities/default.json similarity index 75% rename from src-tauri/capabilities/migrated.json rename to src-tauri/capabilities/default.json index d24b2891..8f3d3e12 100644 --- a/src-tauri/capabilities/migrated.json +++ b/src-tauri/capabilities/default.json @@ -1,18 +1,17 @@ { "$schema": "../gen/schemas/desktop-schema.json", - "identifier": "migrated", + "identifier": "default", "description": "permissions that were migrated from v1", "local": true, "windows": [ - "main" + "*" ], "permissions": [ + "launcher-core:default", "window:allow-close", "window:allow-destroy", - "window:allow-minimize", "window:allow-start-dragging", - "event:default", - "auth:default", - "game:default" + "window:allow-minimize", + "event:default" ] } diff --git a/src-tauri/permissions/auth/default.toml b/src-tauri/permissions/auth/default.toml deleted file mode 100644 index 1e793923..00000000 --- a/src-tauri/permissions/auth/default.toml +++ /dev/null @@ -1,3 +0,0 @@ -[default] -description = "Default permissions for the plugin" -permissions = [ "allow-login-msa" ] diff --git a/src-tauri/permissions/game/default.toml b/src-tauri/permissions/launcher-core/default.toml similarity index 73% rename from src-tauri/permissions/game/default.toml rename to src-tauri/permissions/launcher-core/default.toml index cda1135c..c5f907f4 100644 --- a/src-tauri/permissions/game/default.toml +++ b/src-tauri/permissions/launcher-core/default.toml @@ -3,5 +3,6 @@ description = "Default permissions for the plugin" permissions = [ "allow-get-instance", "allow-get-instances", - "allow-create-instance" + "allow-create-instance", + "allow-login-msa" ] diff --git a/src-tauri/src/constants.rs b/src-tauri/src/constants.rs index f3b6ee28..7927316a 100644 --- a/src-tauri/src/constants.rs +++ b/src-tauri/src/constants.rs @@ -1,12 +1,3 @@ // Basic Metadata pub const NAME: &str = "OneLauncher"; pub const VERSION: &str = "0.1.0"; - -// Authentication and HTTP (TODO: env?) -pub const USER_AGENT: &str = "OneLauncher/1.0.0 (polyfrost.org)"; -pub const CLIENT_ID: &str = "9419b7ee-1448-4d1b-b52a-550d8f36ab56"; - -// Minecraft -pub const MINECRAFT_VERSIONS_MANIFEST: &str = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json"; - -// Java Downloading diff --git a/src-tauri/src/game/plugin.rs b/src-tauri/src/game/plugin.rs deleted file mode 100644 index 5cbc08d2..00000000 --- a/src-tauri/src/game/plugin.rs +++ /dev/null @@ -1,50 +0,0 @@ -use tauri::{plugin::TauriPlugin, Manager, State}; -use tokio::sync::Mutex; -use uuid::Uuid; - -use super::client::{ClientManager, ClientType, Instance}; - -pub struct GameManagerState { - pub client_manager: ClientManager, -} - -unsafe impl Send for GameManagerState {} -unsafe impl Sync for GameManagerState {} - -pub fn init() -> TauriPlugin { - tauri::plugin::Builder::new("game") - .setup(|app, _| { - app.manage(Mutex::new(GameManagerState { - client_manager: ClientManager::new(app).expect("Failed to initialize client manager"), - })); - Ok(()) - }) - .invoke_handler(tauri::generate_handler![ - create_instance, - get_instances, - ]) - .build() -} - -#[tauri::command] -async fn create_instance( - state: State<'_, Mutex>, - name: String, - version: String, - cover: Option, - group: Option, - client: ClientType -) -> Result { - let manager = &mut state.lock().await.client_manager; - - manager.create_instance(name, version, cover, group, client).await.or_else(|e| { - Err(e.to_string()) - }) -} - -#[tauri::command] -async fn get_instances(state: State<'_, Mutex>) -> Result, String> { - let manager = &mut state.lock().await.client_manager; - - Ok(manager.get_instances_owned()) -} diff --git a/src-tauri/src/launcher_core/auth.rs b/src-tauri/src/launcher_core/auth.rs new file mode 100644 index 00000000..536d8216 --- /dev/null +++ b/src-tauri/src/launcher_core/auth.rs @@ -0,0 +1,14 @@ +use launcher_core::auth::{microsoft_auth::MicrosoftAuthenticationMethod, Account, AuthenticationMethod}; +use tauri::Manager; + +#[tauri::command] +pub async fn login_msa(app: tauri::AppHandle) -> Result { + match MicrosoftAuthenticationMethod::auth(|status, stage, was_last| { + let _ = app.emit("msa:status", (status, stage, was_last)); + }) + .await + { + Ok(account) => Ok(account), + Err(err) => Err(err.to_string()), + } +} \ No newline at end of file diff --git a/src-tauri/src/launcher_core/game.rs b/src-tauri/src/launcher_core/game.rs new file mode 100644 index 00000000..8cd51e38 --- /dev/null +++ b/src-tauri/src/launcher_core/game.rs @@ -0,0 +1,29 @@ +use launcher_core::game::client::{ClientType, Instance}; +use tauri::State; +use tokio::sync::Mutex; +use uuid::Uuid; + +use super::GameManagerState; + +#[tauri::command] +pub async fn create_instance( + state: State<'_, Mutex>, + name: String, + version: String, + cover: Option, + group: Option, + client: ClientType +) -> Result { + let manager = &mut state.lock().await.client_manager; + + manager.create_instance(name, version, cover, group, client).await.or_else(|e| { + Err(e.to_string()) + }) +} + +#[tauri::command] +pub async fn get_instances(state: State<'_, Mutex>) -> Result, String> { + let manager = &mut state.lock().await.client_manager; + + Ok(manager.get_instances_owned()) +} \ No newline at end of file diff --git a/src-tauri/src/launcher_core/mod.rs b/src-tauri/src/launcher_core/mod.rs new file mode 100644 index 00000000..e87b1081 --- /dev/null +++ b/src-tauri/src/launcher_core/mod.rs @@ -0,0 +1,30 @@ +use launcher_core::game::client::ClientManager; +use tauri::{plugin::TauriPlugin, Manager}; +use tokio::sync::Mutex; + +mod auth; +mod game; + +pub struct GameManagerState { + pub client_manager: ClientManager, +} + +unsafe impl Send for GameManagerState {} +unsafe impl Sync for GameManagerState {} + +pub fn init() -> TauriPlugin { + tauri::plugin::Builder::new("launcher-core") + .setup(|app, _| { + app.manage(Mutex::new(GameManagerState { + client_manager: ClientManager::new().expect("Failed to initialize client manager"), + })); + + Ok(()) + }) + .invoke_handler(tauri::generate_handler![ + auth::login_msa, + game::create_instance, + game::get_instances, + ]) + .build() +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 2867edb6..da59eca8 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,61 +1,2 @@ -use thiserror::Error; - -pub mod auth; pub mod constants; -pub mod game; -pub mod utils; - -pub struct AppState { - // pub , -} - -/// A standardized [`Error`] type that is used across the launcher -#[derive(Debug, Error)] -pub enum PolyError { - #[error("a tauri runtime error occured (this should not happen): {0}")] - /// Wrapper around [`tauri::Error`] to handle expected Tauri errors. - TauriError(#[from] tauri::Error), - #[error("failed to manage shell task: {0}")] - /// Wrapper around [`tauri_plugin_shell::Error`] to handle shell errors. - TauriShellError(#[from] tauri_plugin_shell::Error), - #[error("failed to manage a tokio semaphore: {0}")] - /// Wrapper around [`tokio::sync::AcquireError`] to handle sempahore errors. - TokioError(#[from] tokio::sync::AcquireError), - #[error("failed to compress a file with flate: {0}")] - /// Wrapper around [`flate2::CompressError`] to handle flate compression errors. - FlateError(#[from] flate2::CompressError), - // im actually the funniest person alive dont @ me - #[error("failed to decompress a file with flate: {0}")] - /// Wrapper around [`flate2::DecompressError`] to handle flate decompression errors. - DeflateError(#[from] flate2::DecompressError), - #[error("failed to parse uuids: {0}")] - /// Wrapper around [`uuid::Error`] to handle UUID parsing errors. - UUIDError(#[from] uuid::Error), - #[error("failed to manage serialization of json: {0}")] - /// Wrapper around [`serde_json::Error`] to handle Serde JSON parsing errors. - SerdeError(#[from] serde_json::Error), - #[error("failed to manage writing and reading of files: {0}")] - /// Wrapper around [`std::io::Error`] only to be used if a more defined error isn't available (e.g. when using `zip`) - IOError(#[from] std::io::Error), - #[error("failed to establish a HTTP connection: {0}")] - /// Wrapper around [`reqwest::Error`] to handle HTTP errors. - HTTPError(#[from] tauri_plugin_http::reqwest::Error), - #[error("failed to download java: {0}")] - /// Wrapper around [`game::client::JavaDownloadError`] to handle Java executable downloading. - JavaError(#[from] game::java::JavaDownloadError), - /// Wrapper around [`zip::result::ZipError`] to handle zip errors. - #[error("failed to manage zip files: {0}")] - ZipError(#[from] zip::result::ZipError), - #[error("failed to authenticate: {0}")] - /// Wrapper around [`auth::AuthenticationError`] to handle authentication errors. - AuthError(#[from] auth::AuthenticationError), - /// Wrapper around [`anyhow::Error`] to handle generic errors. - #[error(transparent)] - AnyhowError(#[from] anyhow::Error), - /// Wrapper around [`chrono::ParseError`] to handle date parsing errors. - #[error("failed to parse a date: {0}")] - ChronoError(#[from] chrono::ParseError), -} - -/// Alias for a [`Result`] with the error type [`PolyError`]. -pub type PolyResult = anyhow::Result; +pub mod launcher_core; diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 0cc2b172..b32a75d9 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -3,15 +3,14 @@ use std::error::Error; -use polyfrost_launcher::{auth, game}; +use polyfrost_launcher::launcher_core; use tauri::{menu::Menu, App}; #[tokio::main] async fn main() { tauri::Builder::default() .plugin(tauri_plugin_shell::init()) - .plugin(auth::init()) - .plugin(game::plugin::init()) + .plugin(launcher_core::init()) .menu(|handle| Menu::new(handle)) .setup(setup) .run(tauri::generate_context!()) diff --git a/src/ui/pages/Browser.tsx b/src/ui/pages/Browser.tsx index dd5c68f8..3edf2bf1 100644 --- a/src/ui/pages/Browser.tsx +++ b/src/ui/pages/Browser.tsx @@ -8,7 +8,7 @@ function BrowserPage() {