Skip to content

Commit

Permalink
feat(glowsquid): ✨ redo state syncing and add connect accounts from b…
Browse files Browse the repository at this point in the history
…ackend to frontend

Signed-off-by: Suyashtnt <[email protected]>
  • Loading branch information
Suya1671 committed Jun 21, 2024
1 parent 4630775 commit 3328a9f
Show file tree
Hide file tree
Showing 10 changed files with 234 additions and 82 deletions.
35 changes: 29 additions & 6 deletions apps/glowsquid-frontend/src/lib/bindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,33 @@
async greet(name: string) : Promise<string> {
return await TAURI_INVOKE("greet", { name });
},
async addAccount() : Promise<Result<MinecraftProfile, Error<AuthError>>> {
/**
* Add a new account
*
* # Returns
* returns the index of the account that was added
*/
async addAccount() : Promise<Result<number, Error<AuthError>>> {
try {
return { status: "ok", data: await TAURI_INVOKE("add_account") };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
/**
* Switch to a different account
*
* # Returns
* returns Ok(()) if the account was switched successfully, otherwise returns Err(())
*/
async switchToAccount(accountIndex: number) : Promise<Result<null, null>> {
try {
return { status: "ok", data: await TAURI_INVOKE("switch_to_account", { accountIndex }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
}
}

Expand All @@ -29,21 +49,24 @@ try {

export type AuthError = "MsToken" | "MinecraftToken" | "MinecraftProfile"
/**
* The state that the frontend will have access to
* State of currently authenticated profiles
*/
export type AuthState = {
/**
* All the profiles that the user has added
*/
export type AuthState = { profiles: MinecraftProfile[];
profiles: MinecraftProfile[];
/**
* Index of the currently selected profile
*/
currentProfile: number | null }
currentProfileIndex: number | null }
export type Cape = { id: string; state: UsageState; url: string; alias: string }
/**
* A custom error type that wraps around a error-stack error, converting it from Report<T> to Error<T>
*
* Also known as the most unholy error type known to crabkind
*/
export type Error<T> = { error: T; report: JsonValue }
export type JsonValue = null | boolean | number | string | JsonValue[] | { [key in string]: JsonValue }
export type Error<T> = { error: T; report: any }
export type MinecraftProfile = { id: string; name: string; skins: Skin[]; capes: Cape[] }
export type Skin = { id: string; state: UsageState; url: string; variant: SkinVariant }
export type SkinVariant = "CLASSIC" | "SLIM"
Expand Down
18 changes: 13 additions & 5 deletions apps/glowsquid-frontend/src/lib/state.svelte.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
import { listen } from "@tauri-apps/api/event";
import { emit, listen } from "@tauri-apps/api/event";
import { Map } from "svelte/reactivity";
import type { AuthState } from './bindings'

const internalStateStore = new Map<string, unknown>();

export const setupStateListener = () => {
listen<{ key: string, value: unknown }>("state_update", (event) => internalStateStore.set(event.payload.key, event.payload.value));
export const setupState = async () => {
listen<{ key: string, value: unknown }>("state_change", (event) => {
const { key, value } = event.payload;
console.debug("state_change", { key, value })
internalStateStore.set(key, value)
});

emit("sync_ready")
}

const tauriState = <T>(key: string, defaultValue: T) => {
internalStateStore.set(key, defaultValue);
if (!internalStateStore.has(key)) {
internalStateStore.set(key, defaultValue);
}

return {
get value() {
Expand All @@ -20,5 +28,5 @@ const tauriState = <T>(key: string, defaultValue: T) => {

export const authState = tauriState<AuthState>("auth", {
profiles: [],
currentProfile: null,
currentProfileIndex: null,
})
3 changes: 3 additions & 0 deletions apps/glowsquid-frontend/src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@
import "@repo/ui/theme/fonts/recursive.css";
import "./app.css";
import type { Snippet } from 'svelte';
import { setupState } from '$lib/state.svelte';
const { children }: { children: Snippet } = $props();
setupState()
</script>


Expand Down
81 changes: 45 additions & 36 deletions apps/glowsquid-frontend/src/routes/accountDropdown.svelte
Original file line number Diff line number Diff line change
@@ -1,26 +1,45 @@
<script lang="ts">
import { createDropdownMenu, melt } from '@melt-ui/svelte'
import { createDropdownMenu, melt, name } from '@melt-ui/svelte'
import { fly } from 'svelte/transition'
import DownArrow from '~icons/material-symbols/keyboard-arrow-down'
import Settings from '~icons/material-symbols/settings-outline'
import Plus from '~icons/material-symbols/add'
import { commands } from '$lib/bindings'
import { authState } from "$lib/state.svelte";
const addAccount = (e: Event) => {
console.log('Add account')
commands.addAccount()
.then((response) => {
if (response.status === 'ok') {
console.log('Account added', response.data)
console.log('Account added', response.data);
switchTo(response.data);
}
else {
console.error('Failed to add account', response.error.error, response.error.report)
console.error('Failed to add account', response.error.error, response.error.report);
}
})
}
const switchTo = (index: number) => (e: Event) => {
console.log('Switch to account', index)
commands.switchToAccount(index)
.then((response) => {
if (response.status === 'ok') {
console.log('Account switched')
}
else {
console.error('Failed to switch account')
}
})
}
const profiles = $derived(authState.value.profiles)
const currentProfile = $derived(authState.value.currentProfileIndex !== null ? profiles[authState.value.currentProfileIndex] : null)
const {
elements: { trigger, menu, item, separator },
states: { open },
Expand All @@ -30,19 +49,27 @@
})
</script>

<button
class="dropdown-button"
use:melt={$trigger}
aria-label="Open account dropdown"
>
{#snippet account(account: string)}
<img
alt="TNTMan1671's avatar"
src="https://minepic.org/avatar/TNTMan1671/32"
alt="{account}'s avatar"
src="https://minepic.org/avatar/{account}/32"
width="32"
height="32"
>

<p>TNTMan1671</p>
<p>{account}</p>
{/snippet}

<button
class="dropdown-button"
use:melt={$trigger}
aria-label="Open account dropdown"
>
{#if currentProfile}
{@render account(currentProfile.name)}
{:else}
<p>No Account</p>
{/if}

<DownArrow class="account-arrow" />
</button>
Expand All @@ -52,31 +79,13 @@
use:melt={$menu}
transition:fly={{ duration: 150, y: -10 }}
>
<li use:melt={$item}>
<button>
<img
alt="TNTMan1671's avatar"
src="https://minepic.org/avatar/TNTMan1671/32"
width="32"
height="32"
>

<p>TNTMan1671</p>
</button>
</li>

<li use:melt={$item}>
<button>
<img
alt="KrazyMiner001's avatar"
src="https://minepic.org/avatar/KrazyMiner001/32"
width="32"
height="32"
>

<p>KrazyMiner001</p>
</button>
</li>
{#each profiles as profile, idx}
<li use:melt={$item}>
<button onclick={switchTo(idx)}>
{@render account(profile.name)}
</button>
</li>
{/each}

<hr use:melt={$separator}>

Expand Down
42 changes: 33 additions & 9 deletions apps/glowsquid/src/auth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,45 @@ pub mod state;
use std::{future::IntoFuture, sync::Arc};

use axum::{routing, Router};
use copper::client::auth::{MicrosoftAuthenticator, MinecraftProfile};
use copper::client::auth::MicrosoftAuthenticator;
use tauri::{async_runtime, AppHandle};
use tokio::net::TcpListener;

use error_stack::ResultExt;

use crate::error::Error;

// Glowsquids details. If you're making your own app, please do not use these.
// These are just for ease of use/packaging.
const CLIENT_ID: &str = "2aa32806-92e3-4242-babc-392ac0f0fd30";
const CLIENT_SECRET: &str = "nky8Q~8lORwTC0OjdxVsgZSs0hCdTcEdec3hNbaP";

#[tauri::command]
#[specta::specta]
/// Switch to a different account
///
/// # Returns
/// returns Ok(()) if the account was switched successfully, otherwise returns Err(())
pub async fn switch_to_account(
state: tauri::State<'_, state::State>,
account_index: u8,
) -> Result<(), ()> {
if state.switch_to(account_index).await {
Ok(())
} else {
Err(())
}
}

#[tauri::command]
#[specta::specta]
/// Add a new account
///
/// # Returns
/// returns the index of the account that was added
pub async fn add_account(
state: tauri::State<'_, state::State>,
app: AppHandle,
) -> Result<MinecraftProfile, crate::error::Error<server::AuthError>> {
) -> Result<u8, Error<server::AuthError>> {
// setup the server
let listener = TcpListener::bind(("127.0.0.1", 0)).await.unwrap();
let port = listener.local_addr().unwrap().port();
Expand Down Expand Up @@ -54,16 +76,18 @@ pub async fn add_account(

open::that(authentication_info.url.as_str()).expect("Failed to open browser");

let minecraft_token = recv_token.recv().await.unwrap()?;

// TODO: save token to database and update state
let token = recv_token.recv().await.unwrap()?;

// shutdown the server
server_task.abort();

oauth
.get_minecraft_profile(&minecraft_token)
let profile = oauth
.get_minecraft_profile(&token)
.await
.change_context(server::AuthError::MinecraftProfile)
.map_err(Into::into)
.map_err(Into::<Error<_>>::into)?;

let idx = state.add_account(token, profile).await;

Ok(idx)
}
38 changes: 34 additions & 4 deletions apps/glowsquid/src/auth/state.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::state;
use copper::client::auth::MinecraftProfile;
use copper::client::auth::{MinecraftProfile, MinecraftToken};
use once_cell::sync::OnceCell;
use serde::Serialize;
use specta::Type;
Expand All @@ -16,11 +16,12 @@ pub struct State {

#[derive(Clone, Serialize, Type)]
#[serde(rename_all = "camelCase")]
/// The state that the frontend will have access to
/// State of currently authenticated profiles
pub struct AuthState {
/// All the profiles that the user has added
profiles: Vec<MinecraftProfile>,
/// Index of the currently selected profile
current_profile: Option<u8>,
current_profile_index: Option<u8>,
}

impl State {
Expand All @@ -38,7 +39,7 @@ impl State {
// TODO: load profiles from database
let frontend_state = AuthState {
profiles: vec![],
current_profile: None,
current_profile_index: None,
};

Self {
Expand All @@ -47,4 +48,33 @@ impl State {
frontend_state: state::State::new("auth", frontend_state, app_handle.clone()),
}
}

pub async fn sync(&self) {
self.frontend_state.sync().await;
}

pub async fn switch_to(&self, account_index: u8) -> bool {
let mut state = self.frontend_state.lock().await;

if account_index >= state.profiles.len() as u8 {
return false;
}

state.current_profile_index = Some(account_index);

true
}

pub async fn add_account(&self, token: MinecraftToken, profile: MinecraftProfile) -> u8 {
let mut state = self.frontend_state.lock().await;

// TODO: save token to database

if let Some(idx) = state.profiles.iter().position(|p| p.id() == profile.id()) {
return idx.try_into().unwrap();
}
state.profiles.push(profile);

state.profiles.len() as u8 - 1
}
}
Loading

0 comments on commit 3328a9f

Please sign in to comment.