refactor: Split logic into its own crate

This commit is contained in:
LynithDev 2024-03-24 11:58:54 +01:00
parent b9894ec736
commit 3fecf109c4
28 changed files with 268 additions and 225 deletions

View file

@ -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

27
core/Cargo.toml Normal file
View file

@ -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 }

View file

@ -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<R: Runtime, F>(handle: &AppHandle<R>, stage: F) -> PolyResult<Account>
async fn auth<F>(stage: F) -> PolyResult<Account>
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<String> {
.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<String> {
Ok(token["access_token"].as_str().unwrap().to_string())
}
const PORT: u16 = 13523;
async fn msa_code<R: Runtime>(handle: &AppHandle<R>) -> PolyResult<String> {
async fn msa_code() -> PolyResult<String> {
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 {

View file

@ -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<R: Runtime, F>(handle: &AppHandle<R>, stage: F) -> PolyResult<Account>
async fn auth<F>(stage: F) -> PolyResult<Account>
where
F: Fn(String, u8, bool) -> ();
async fn get_profile(access_token: String) -> PolyResult<Account> {
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<R: tauri::Runtime>() -> TauriPlugin<R> {
tauri::plugin::Builder::new("auth")
.invoke_handler(tauri::generate_handler![login_msa])
.build()
}
#[tauri::command]
async fn login_msa<R: tauri::Runtime>(app: tauri::AppHandle<R>) -> Result<Account, String> {
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()),
}
}

12
core/src/constants.rs Normal file
View file

@ -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

View file

@ -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<dyn ClientTrait<'a> + 'a> where Self: Sized {
fn get_impl(client: &'a ClientType, instance: &'a Instance, manifest: &'a Manifest) -> Box<dyn ClientTrait<'a> + '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<Uuid, Instance>,
instances_dir: PathBuf,
manifests: HashMap<Uuid, Manifest>,
manifests_dir: PathBuf,
}
impl ClientManager {
pub fn new(handle: &AppHandle) -> PolyResult<Self> {
let instances_dir = handle.path().app_config_dir()?.join("instances");
let instances = load_and_serialize::<Instance>(&instances_dir)?;
pub fn new() -> PolyResult<Self> {
let instances = load_and_serialize::<Instance>(
&dirs::instances_dir()?
)?;
let manifests_dir = handle.path().app_config_dir()?.join("manifests");
let manifests = load_and_serialize::<Manifest>(&manifests_dir)?;
let manifests = load_and_serialize::<Manifest>(
&dirs::manifests_dir()?
)?;
Ok(Self {
handle: handle.clone(),
instances,
instances_dir,
manifests,
manifests_dir,
})
}
@ -177,7 +155,7 @@ impl ClientManager {
group: Option<Uuid>,
client: ClientType
) -> PolyResult<Uuid> {
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)

View file

@ -1,5 +1,4 @@
pub mod client;
pub mod java;
pub mod vanilla;
pub mod plugin;
pub mod minecraft;

View file

@ -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
}

65
core/src/lib.rs Normal file
View file

@ -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<T> = anyhow::Result<T, PolyError>;

35
core/src/utils/dirs.rs Normal file
View file

@ -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<PathBuf> {
dirs::config_dir()
.and_then(|dir| Some(dir.join(constants::APP_CONFIG_DIR)))
.ok_or(DirectoryError::ConfigDir.into())
}
pub fn libraries_dir() -> PolyResult<PathBuf> {
Ok(app_config_dir()?.join("libraries"))
}
pub fn java_dir() -> PolyResult<PathBuf> {
Ok(app_config_dir()?.join("java"))
}
pub fn instances_dir() -> PolyResult<PathBuf> {
Ok(app_config_dir()?.join("instances"))
}
pub fn manifests_dir() -> PolyResult<PathBuf> {
Ok(app_config_dir()?.join("manifests"))
}

View file

@ -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};

View file

@ -1,2 +1,3 @@
pub mod file;
pub mod http;
pub mod dirs;

View file

@ -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!!

View file

@ -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",
]),
),
);

View file

@ -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"
]
}

View file

@ -1,3 +0,0 @@
[default]
description = "Default permissions for the plugin"
permissions = [ "allow-login-msa" ]

View file

@ -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"
]

View file

@ -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

View file

@ -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::Wry> {
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<GameManagerState>>,
name: String,
version: String,
cover: Option<String>,
group: Option<Uuid>,
client: ClientType
) -> Result<Uuid, String> {
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<GameManagerState>>) -> Result<Vec<Instance>, String> {
let manager = &mut state.lock().await.client_manager;
Ok(manager.get_instances_owned())
}

View file

@ -0,0 +1,14 @@
use launcher_core::auth::{microsoft_auth::MicrosoftAuthenticationMethod, Account, AuthenticationMethod};
use tauri::Manager;
#[tauri::command]
pub async fn login_msa<R: tauri::Runtime>(app: tauri::AppHandle<R>) -> Result<Account, String> {
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()),
}
}

View file

@ -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<GameManagerState>>,
name: String,
version: String,
cover: Option<String>,
group: Option<Uuid>,
client: ClientType
) -> Result<Uuid, String> {
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<GameManagerState>>) -> Result<Vec<Instance>, String> {
let manager = &mut state.lock().await.client_manager;
Ok(manager.get_instances_owned())
}

View file

@ -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::Wry> {
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()
}

View file

@ -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<T> = anyhow::Result<T, PolyError>;
pub mod launcher_core;

View file

@ -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!())

View file

@ -8,7 +8,7 @@ function BrowserPage() {
<Button
// eslint-disable-next-line solid/reactivity
onClick={async () => {
await invoke('plugin:game|launch_game');
await invoke('plugin:launcher-core|launch_game');
// eslint-disable-next-line no-console
console.log(`Game exited`);
}}
@ -18,7 +18,7 @@ function BrowserPage() {
<Button
onClick={async () => {
const result = invoke('plugin:game|get_instances');
const result = invoke('plugin:launcher-core|get_instances');
console.log(result);
}}
>
@ -27,7 +27,7 @@ function BrowserPage() {
<Button
onClick={async () => {
const result = await invoke('plugin:game|create_instance', {
const result = await invoke('plugin:launcher-core|create_instance', {
name: 'My epic instance name',
version: '1.8.9',
client: {