refactor: Split logic into its own crate
This commit is contained in:
parent
b9894ec736
commit
3fecf109c4
28 changed files with 268 additions and 225 deletions
|
|
@ -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
27
core/Cargo.toml
Normal 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 }
|
||||
|
|
@ -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 {
|
||||
|
|
@ -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
12
core/src/constants.rs
Normal 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
|
||||
|
|
@ -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)
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
pub mod client;
|
||||
pub mod java;
|
||||
pub mod vanilla;
|
||||
pub mod plugin;
|
||||
pub mod minecraft;
|
||||
|
|
@ -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
65
core/src/lib.rs
Normal 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
35
core/src/utils/dirs.rs
Normal 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"))
|
||||
}
|
||||
|
|
@ -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};
|
||||
|
||||
|
|
@ -1,2 +1,3 @@
|
|||
pub mod file;
|
||||
pub mod http;
|
||||
pub mod dirs;
|
||||
|
|
@ -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!!
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
]),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
[default]
|
||||
description = "Default permissions for the plugin"
|
||||
permissions = [ "allow-login-msa" ]
|
||||
|
|
@ -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"
|
||||
]
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
14
src-tauri/src/launcher_core/auth.rs
Normal file
14
src-tauri/src/launcher_core/auth.rs
Normal 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()),
|
||||
}
|
||||
}
|
||||
29
src-tauri/src/launcher_core/game.rs
Normal file
29
src-tauri/src/launcher_core/game.rs
Normal 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())
|
||||
}
|
||||
30
src-tauri/src/launcher_core/mod.rs
Normal file
30
src-tauri/src/launcher_core/mod.rs
Normal 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()
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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!())
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue