diff --git a/apps/desktop/build.rs b/apps/desktop/build.rs index 05964c47..eef73914 100644 --- a/apps/desktop/build.rs +++ b/apps/desktop/build.rs @@ -6,8 +6,7 @@ fn main() { "onelauncher", tauri_build::InlinedPlugin::new().commands(&[ // User - "begin_msa", - "finish_msa", + "msa_auth", "get_users", "get_user", "remove_user", diff --git a/apps/desktop/permissions/onelauncher/default.toml b/apps/desktop/permissions/onelauncher/default.toml index c9fd51d7..ee8b0046 100644 --- a/apps/desktop/permissions/onelauncher/default.toml +++ b/apps/desktop/permissions/onelauncher/default.toml @@ -2,8 +2,7 @@ description = "Default permissions for the plugin" permissions = [ # User - "allow-begin-msa", - "allow-finish-msa", + "allow-msa-auth", "allow-get-users", "allow-get-user", "allow-remove-user", diff --git a/apps/desktop/src/api/commands.rs b/apps/desktop/src/api/commands.rs index 5c1fa18c..7366c0b4 100644 --- a/apps/desktop/src/api/commands.rs +++ b/apps/desktop/src/api/commands.rs @@ -4,10 +4,12 @@ use interpulse::api::minecraft::Version; use onelauncher::constants::{NATIVE_ARCH, TARGET_OS, VERSION}; use onelauncher::data::{Loader, ManagedPackage, MinecraftCredentials, PackageData, Settings}; use onelauncher::package::content; -use onelauncher::store::{Cluster, ClusterPath, MinecraftLogin}; +use onelauncher::store::{Cluster, ClusterPath}; use onelauncher::{cluster, minecraft, processor, settings}; use serde::{Deserialize, Serialize}; use specta::Type; +use tauri::AppHandle; +use tauri_plugin_shell::ShellExt; use uuid::Uuid; #[macro_export] @@ -21,8 +23,7 @@ macro_rules! collect_commands { ) .commands(tauri_specta::collect_commands![ // User - begin_msa, - finish_msa, + msa_auth, get_users, get_user, remove_user, @@ -110,16 +111,6 @@ pub async fn run_cluster(uuid: Uuid) -> Result<(Uuid, u32), String> { .id() .unwrap_or(0); - // tokio::task::spawn(async move { - // let mut proc = c_lock.write().await; - // if let Err(err) = processor::wait_for(&mut proc).await { - // tracing::error!("Error waiting for process: {:?}", err); - // }; - // }); - - // let mut proc = c_lock.write().await; - // processor::wait_for(&mut proc).await?; - Ok((p_uuid, p_pid)) } @@ -160,12 +151,6 @@ pub async fn get_cluster(uuid: Uuid) -> Result { #[specta::specta] #[tauri::command] pub async fn get_clusters() -> Result, String> { - // let mut map = HashMap::::new(); - // let cluster = placeholder_cluster(); - // map.insert(cluster.cluster_path(), cluster); - - // Ok(map) - Ok(cluster::list(None).await?) } @@ -230,17 +215,13 @@ pub async fn get_user(uuid: Uuid) -> Result { #[specta::specta] #[tauri::command] -pub async fn begin_msa() -> Result { - Ok(minecraft::begin().await?) -} +pub async fn msa_auth(handle: AppHandle) -> Result { + let login = minecraft::begin().await?; + handle.shell().open(login.redirect_uri.clone(), None).map_err(|err| err.to_string())?; -#[specta::specta] -#[tauri::command] -pub async fn finish_msa( - code: String, - login: MinecraftLogin, -) -> Result { - Ok(minecraft::finish(code.as_str(), login).await?) + Err("not implemented".to_string()) + + // Ok(minecraft::finish(code.as_str(), login).await?) } #[specta::specta] diff --git a/apps/frontend/src/ui/components/ScrollableContainer.tsx b/apps/frontend/src/ui/components/ScrollableContainer.tsx index 5d2c69cc..eca98297 100644 --- a/apps/frontend/src/ui/components/ScrollableContainer.tsx +++ b/apps/frontend/src/ui/components/ScrollableContainer.tsx @@ -5,7 +5,7 @@ function ScrollableContainer(props: ParentProps) { return (
-
+
{props.children}
diff --git a/apps/frontend/src/ui/components/base/Button.module.scss b/apps/frontend/src/ui/components/base/Button.module.scss index b282cde7..50b291db 100644 --- a/apps/frontend/src/ui/components/base/Button.module.scss +++ b/apps/frontend/src/ui/components/base/Button.module.scss @@ -16,6 +16,10 @@ @apply bg-danger hover:bg-danger-hover active:bg-danger-pressed disabled:bg-danger-disabled; } + &__iconDanger { + @apply bg-op-10; + } + &__ghost { @apply hover:bg-gray-05 [&[aria-checked="true"]]:bg-gray-10 active:bg-gray-10; } @@ -54,4 +58,4 @@ .icon > * { @apply w-4; -} \ No newline at end of file +} diff --git a/apps/frontend/src/ui/pages/settings/game/SettingsAccounts.tsx b/apps/frontend/src/ui/pages/settings/game/SettingsAccounts.tsx index c1d1e457..df8382ed 100644 --- a/apps/frontend/src/ui/pages/settings/game/SettingsAccounts.tsx +++ b/apps/frontend/src/ui/pages/settings/game/SettingsAccounts.tsx @@ -1,84 +1,158 @@ -import { ChevronRightIcon, InfoCircleIcon, UserPlus02Icon } from '@untitled-theme/icons-solid'; -import { For, Show, createSignal, mergeProps } from 'solid-js'; +import { InfoCircleIcon, LinkExternal01Icon, Trash01Icon, UserPlus02Icon } from '@untitled-theme/icons-solid'; +import { type Accessor, For, Match, type Setter, Show, Switch, createEffect, createSignal, mergeProps } from 'solid-js'; import { bridge } from '~imports'; import Button from '~ui/components/base/Button'; import Tooltip from '~ui/components/base/Tooltip'; import PlayerHead from '~ui/components/game/PlayerHead'; +import Modal from '~ui/components/overlay/Modal'; import ScrollableContainer from '~ui/components/ScrollableContainer'; import Sidebar from '~ui/components/Sidebar'; - -const Accounts: { - username: string; - uuid: string; -}[] = [ - { - username: 'Lynith', - uuid: 'f247be7c-5b82-41c6-9148-793ded77e71f', - }, - { - username: 'Wyvest', - uuid: 'a5331404-0e77-440e-8bef-24c071dac1ae', - }, - { - username: 'nextday', - uuid: '000646f6-b779-419c-b5d6-2c5ba52419bf', - }, - { - username: 'xXfake_playerXx', - uuid: '000646f6-b779-419c-b5d6-aaa', - }, -]; +import useCommand from '~ui/hooks/useCommand'; function SettingsAccounts() { const [current, setCurrent] = createSignal(); + const [accounts, { refetch }] = useCommand(bridge.commands.getUsers); + const [modalVisible, setModalVisible] = createSignal(false); + + createEffect(() => { + const users = accounts(); + if (users === undefined || users.length < 1 || current() !== undefined) + return; + + setCurrent(users[0]!.id); + }); + + function onDelete(uuid: string) { + bridge.commands.removeUser(uuid) + .finally(() => { + refetch(); + }); + } + + function showModal() { + setModalVisible(true); + } return (

Accounts

+ - - - {account => ( - - )} - - + + +
+ No accounts added. + Add one with the Add Account button. +
+
+ + + {account => ( + + )} + + +
+
+ + +
); } +enum ModalStage { + Tasks, + WaitingForCode, + LoggingIn, +} + +interface AddAccountModalProps { + visible: Accessor; + setVisible: Setter; + refetch: () => any; +} + +function AddAccountModal(props: AddAccountModalProps) { + const [stage, setStage] = createSignal(ModalStage.Tasks); + + function start() { + setStage(ModalStage.WaitingForCode); + + // tryResult(bridge.commands.beginMsa).then((res) => { + // console.log(res); + // }); + } + + function finish() { + props.setVisible(false); + props.refetch(); + } + + return ( + props.setVisible(false)} + />, +
diff --git a/apps/frontend/src/ui/pages/settings/launcher/SettingsAppearance.tsx b/apps/frontend/src/ui/pages/settings/launcher/SettingsAppearance.tsx index b116f41a..5eabe453 100644 --- a/apps/frontend/src/ui/pages/settings/launcher/SettingsAppearance.tsx +++ b/apps/frontend/src/ui/pages/settings/launcher/SettingsAppearance.tsx @@ -13,7 +13,7 @@ function SettingsAppearance() {

Appearance

-
+

theme placeholder

diff --git a/packages/core/src/constants.rs b/packages/core/src/constants.rs index 1234209b..6d5760bd 100644 --- a/packages/core/src/constants.rs +++ b/packages/core/src/constants.rs @@ -23,6 +23,8 @@ pub const DUMMY_REPLACE_NEWLINE: &str = "\n"; pub const DISCORD_RPC_CLIENT_ID: &str = "1234567890000000"; // todo /// Our Microsoft client ID. pub const MICROSOFT_CLIENT_ID: &str = "9eac3a4e-8cdd-43ef-863e-49cd601b1f03"; +/// The port to run the localhost server on. +pub const MSA_SERVER_PORT: u16 = 13523; /// Mojang/Microsoft client ID. pub const MINECRAFT_CLIENT_ID: &str = "00000000402b5328"; /// Mojang/Microsoft login redirect URI. diff --git a/packages/core/src/store/auth_server.html b/packages/core/src/store/auth_server.html new file mode 100644 index 00000000..fa7c9a16 --- /dev/null +++ b/packages/core/src/store/auth_server.html @@ -0,0 +1,33 @@ + + + + + + OneLauncher + + + +

%TITLE%

+

%CONTENT%

+ + diff --git a/packages/core/src/store/minecraft.rs b/packages/core/src/store/minecraft.rs index 2a74aae3..8eb3b229 100644 --- a/packages/core/src/store/minecraft.rs +++ b/packages/core/src/store/minecraft.rs @@ -310,6 +310,89 @@ impl MinecraftState { } } +// pub struct LocalServer { +// listener: TcpListener, +// sender: tokio::sync::mpsc::Sender, +// receiver: tokio::sync::mpsc::Receiver, +// } + +// impl LocalServer { +// pub async fn new() -> crate::Result { +// let listener = TcpListener::bind(format!("127.0.0.1:{}", constants::MSA_SERVER_PORT)).await?; +// let (sender, receiver) = tokio::sync::mpsc::channel::(1); + +// Ok(Self { +// listener, +// sender, +// receiver, +// }) +// } + +// pub async fn wait_for_code(&self) -> crate::Result { + +// } + +// async fn handle_request(&self, stream: &mut TcpStream) -> crate::Result { +// use tokio::io::{AsyncReadExt, AsyncWriteExt}; +// let code: Option; + +// let code_result = { +// let mut buf = vec![0; 1024]; +// let size = stream.read(&mut buf).await?; +// if size == 0 { +// return Err(MinecraftAuthError::MSACodeFetchError.into()); +// } + +// let request = String::from_utf8_lossy(&buf[..size]); +// code = Some( +// request +// .split(' ') +// .find(|x| x.starts_with("code=")) +// .ok_or(anyhow::anyhow!("No code found"))? +// .split('=') +// .nth(1) +// .ok_or(anyhow::anyhow!("No code found"))? +// .to_string() +// ); + +// Ok::<(), String>(()) +// }; + +// let errored = code_result.is_err(); +// let html = include_str!("./auth_server.html"); +// let result = match code_result { +// Ok(()) => { +// html +// .replace("%TITLE%", "Successfully Authenticated") +// .replace("%CONTENT%", "You can now close this tab.") +// } +// Err(e) => { +// html +// .replace("%TITLE%", "Error") +// .replace("%CONTENT%", &format!("An error has occurred: {}", e.as_str())) +// } +// }; + +// let response = format!( +// "HTTP/1.1 {}\r\nContent-Length: {}\r\n\r\n{}", +// if errored { "400 Bad Request" } else { "200 OK" }, +// result.len(), +// result +// ); + +// stream.write_all(response.as_bytes()).await?; + +// if errored { +// return Err(MinecraftAuthError::MSACodeFetchError.into()); +// } + +// match code { +// Some(code) => Ok(code), +// None => Err(MinecraftAuthError::MSACodeFetchError.into()), +// } +// } +// } + /// A [`reqwest::Request`] with a [`DateTime`] attached to it. struct RequestWithDate { pub date: DateTime, @@ -1035,4 +1118,6 @@ pub enum MinecraftAuthError { HashError, #[error("failed to read user xbox session ID")] SessionIdError, + #[error("failed to read user MSA code")] + MSACodeFetchError, }