diff --git a/public/panorama/hellishends_Panorama_Background_Day.png b/public/panorama/hellishends_Panorama_Background_Day.png index e856bf1..7220025 100644 Binary files a/public/panorama/hellishends_Panorama_Background_Day.png and b/public/panorama/hellishends_Panorama_Background_Day.png differ diff --git a/public/panorama/hellishends_Panorama_Background_Night.png b/public/panorama/hellishends_Panorama_Background_Night.png index e856bf1..7220025 100644 Binary files a/public/panorama/hellishends_Panorama_Background_Night.png and b/public/panorama/hellishends_Panorama_Background_Night.png differ diff --git a/src/components/views/LceOnlineView.tsx b/src/components/views/LceOnlineView.tsx index 83b0e04..01cc762 100644 --- a/src/components/views/LceOnlineView.tsx +++ b/src/components/views/LceOnlineView.tsx @@ -1,13 +1,22 @@ import { useState, useEffect, useRef, useMemo, memo } from "react"; import { motion, AnimatePresence } from "framer-motion"; -import { useUI, useConfig, useAudio, useGame } from "../../context/LauncherContext"; +import { + useUI, + useConfig, + useAudio, + useGame, +} from "../../context/LauncherContext"; import ChooseInstanceModal from "../modals/ChooseInstanceModal"; import { lceOnlineService } from "../../services/LceOnlineService"; import { TauriService } from "../../services/TauriService"; interface LceOnlineViewProps { addFriendTarget?: string | null; onClearAddFriendTarget?: () => void; - invites?: Array<{ inviteid: string; from: { uuid: string; username: string; }; sessionid: string; }>; + invites?: Array<{ + inviteid: string; + from: { uuid: string; username: string }; + sessionid: string; + }>; } const LceOnlineView = memo(function LceOnlineView({ addFriendTarget, @@ -19,9 +28,9 @@ const LceOnlineView = memo(function LceOnlineView({ const { playPressSound, playBackSound } = useAudio(); const game = useGame(); const [isSignedIn, setIsSignedIn] = useState(lceOnlineService.signedIn); - const [currentTab, setCurrentTab] = useState<"friends" | "requests" | "invites">( - "friends", - ); + const [currentTab, setCurrentTab] = useState< + "friends" | "requests" | "invites" + >("friends"); const [focusIndex, setFocusIndex] = useState(0); const [friends, setFriends] = useState([]); const [incomingReqs, setIncomingReqs] = useState([]); @@ -32,13 +41,11 @@ const LceOnlineView = memo(function LceOnlineView({ const [addFriendUsername, setAddFriendUsername] = useState(""); const addFriendInputRef = useRef(null); const [errorModal, setErrorModal] = useState(null); - const [joinTarget, setJoinTarget] = useState<{ inviteid: string, sessionId: string; hostName: string } | null>(null); - const [authMode, setAuthMode] = useState<"login" | "register">("login"); - const [authUsername, setAuthUsername] = useState(""); - const [authPassword, setAuthPassword] = useState(""); - const [isAuthLoading, setIsAuthLoading] = useState(false); - const authUserRef = useRef(null); - const authPassRef = useRef(null); + const [joinTarget, setJoinTarget] = useState<{ + inviteid: string; + sessionId: string; + hostName: string; + } | null>(null); const containerRef = useRef(null); const scrollRef = useRef(null); const fetchSocialData = async () => { @@ -59,6 +66,20 @@ const LceOnlineView = memo(function LceOnlineView({ } }, [isSignedIn]); + useEffect(() => { + return lceOnlineService.onSessionChange(() => { + setIsSignedIn(lceOnlineService.signedIn); + }); + }, []); + + useEffect(() => { + if (!isSignedIn) { + TauriService.openUrl( + "https://mclegacyedition.xyz/internal/auth?appId=emerald_launcher", + ); + } + }, []); + useEffect(() => { if (!addFriendTarget) return; setCurrentTab("friends"); @@ -72,16 +93,6 @@ const LceOnlineView = memo(function LceOnlineView({ setIsSignedIn(false); }; - const handleAction = async (action: () => Promise) => { - playPressSound(); - try { - await action(); - fetchSocialData(); - } catch (e: unknown) { - setErrorModal(e instanceof Error ? e.message : "An error occurred"); - } - }; - const handleStartHosting = async () => { playPressSound(); try { @@ -104,20 +115,13 @@ const LceOnlineView = memo(function LceOnlineView({ setIsHosting(false); }; - const handleAuth = async () => { - if (!authUsername.trim() || !authPassword.trim()) return; - setIsAuthLoading(true); + const handleAction = async (action: () => Promise) => { + playPressSound(); try { - if (authMode === "login") { - await lceOnlineService.login(authUsername.trim(), authPassword); - } else { - await lceOnlineService.register(authUsername.trim(), authPassword); - } - setIsSignedIn(true); + await action(); + fetchSocialData(); } catch (e: unknown) { - setErrorModal(e instanceof Error ? e.message : "Authentication failed"); - } finally { - setIsAuthLoading(false); + setErrorModal(e instanceof Error ? e.message : "An error occurred"); } }; @@ -203,11 +207,19 @@ const LceOnlineView = memo(function LceOnlineView({ label: inv.from.username, onClick: () => handleAction(async () => { - const sessionId = await lceOnlineService.acceptInvite(inv.from.username); - setJoinTarget({ inviteid: inv.inviteid, sessionId, hostName: inv.from.username }); + const sessionId = await lceOnlineService.acceptInvite( + inv.from.username, + ); + setJoinTarget({ + inviteid: inv.inviteid, + sessionId, + hostName: inv.from.username, + }); }), onClickSecondary: () => - handleAction(() => lceOnlineService.declineInvite(inv.from.username)), + handleAction(() => + lceOnlineService.declineInvite(inv.from.username), + ), }); }); } @@ -223,7 +235,11 @@ const LceOnlineView = memo(function LceOnlineView({ isHosting, ]); - const tabs: ("friends" | "requests" | "invites")[] = ["friends", "requests", "invites"]; + const tabs: ("friends" | "requests" | "invites")[] = [ + "friends", + "requests", + "invites", + ]; useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (errorModal) { @@ -254,19 +270,6 @@ const LceOnlineView = memo(function LceOnlineView({ setActiveView("main"); return; } - if (e.key === "Tab") { - e.preventDefault(); - if (document.activeElement === authUserRef.current) { - authPassRef.current?.focus(); - } else { - authUserRef.current?.focus(); - } - return; - } - if (e.key === "Enter") { - handleAuth(); - return; - } return; } @@ -318,10 +321,7 @@ const LceOnlineView = memo(function LceOnlineView({ isAddingFriend, addFriendUsername, errorModal, - handleAuth, isSignedIn, - authUsername, - authPassword, ]); useEffect(() => { @@ -348,60 +348,16 @@ const LceOnlineView = memo(function LceOnlineView({ if (!isSignedIn) { return (
-

- {authMode === "login" ? "Sign In" : "Register"} +

+ LCE Online

- setAuthUsername(e.target.value)} - disabled={isAuthLoading} - /> - setAuthPassword(e.target.value)} - disabled={isAuthLoading} - /> -
- - -
+

+ Awaiting authentication... +

); } @@ -440,7 +396,11 @@ const LceOnlineView = memo(function LceOnlineView({
- {currentTab === "friends" ? "Friends" : currentTab === "invites" ? "Invites" : "Pending Requests"} + {currentTab === "friends" + ? "Friends" + : currentTab === "invites" + ? "Invites" + : "Pending Requests"} {listItems.length}
diff --git a/src/hooks/useLceOnlineNotifications.ts b/src/hooks/useLceOnlineNotifications.ts index 0f6617e..ad45415 100644 --- a/src/hooks/useLceOnlineNotifications.ts +++ b/src/hooks/useLceOnlineNotifications.ts @@ -1,9 +1,17 @@ import { useState, useEffect, useRef } from "react"; import { lceOnlineService } from "../services/LceOnlineService"; export function useLceOnlineNotifications() { - const [friendRequestMessage, setFriendRequestMessage] = useState(null); + const [friendRequestMessage, setFriendRequestMessage] = useState< + string | null + >(null); const [InviteMessage, setInviteMessage] = useState(null); - const [invites, setInvites] = useState>([]); + const [invites, setInvites] = useState< + Array<{ + inviteid: string; + from: { uuid: string; username: string }; + sessionid: string; + }> + >([]); const seenRequests = useRef>(new Set()); const seenInvites = useRef>(new Set()); useEffect(() => { @@ -19,17 +27,17 @@ export function useLceOnlineNotifications() { setFriendRequestMessage(`New Friend request from ${r}`); } }); - } catch (e) { } + } catch (e) {} try { const invitesData = await lceOnlineService.getInvites(); setInvites(invitesData); invitesData.forEach((i) => { - if (!seenInvites.current.has(i.inviteid)) { - seenInvites.current.add(i.inviteid); - setInviteMessage(`New invite from ${i.from.username}`); - } - }); - } catch { } + if (!seenInvites.current.has(i.inviteid)) { + seenInvites.current.add(i.inviteid); + setInviteMessage(`New invite from ${i.from.username}`); + } + }); + } catch {} }; const init = async () => { @@ -42,19 +50,19 @@ export function useLceOnlineNotifications() { setFriendRequestMessage(`New Friend request from ${r}`); } }); - } catch (e) { } + } catch (e) {} try { const invitesData = await lceOnlineService.getInvites(); setInvites(invitesData); invitesData.forEach((i) => { - if (!seenInvites.current.has(i.inviteid)) { - seenInvites.current.add(i.inviteid); - setInviteMessage(`New invite from ${i.from.username}`); - } - }); - } catch { } + if (!seenInvites.current.has(i.inviteid)) { + seenInvites.current.add(i.inviteid); + setInviteMessage(`New invite from ${i.from.username}`); + } + }); + } catch {} } - pollInterval = setInterval(poll, 10000); + pollInterval = setInterval(poll, 3000); }; init(); @@ -68,6 +76,6 @@ export function useLceOnlineNotifications() { InviteMessage, clearFriendRequestMessage: () => setFriendRequestMessage(null), clearInviteMessage: () => setInviteMessage(null), - invites + invites, }; } diff --git a/src/pages/App.tsx b/src/pages/App.tsx index 172751f..a8a32af 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -35,6 +35,7 @@ import { useSkin, } from "../context/LauncherContext"; import { TauriService } from "../services/TauriService"; +import { lceOnlineService } from "../services/LceOnlineService"; import { useLceOnlineNotifications } from "../hooks/useLceOnlineNotifications"; import { usePluginViews } from "../plugins/PluginContext"; import { usePlatform } from "../hooks/usePlatform"; @@ -158,16 +159,22 @@ export default function App() { return; } - if ( - action === "lceonline" && - parts.length >= 2 && - parts[1] === "addfriend" - ) { - const username = parsed.searchParams.get("username"); - if (username) { - setActiveView("lceonline"); - setAddFriendTarget(username); - return; + if (action === "lceonline" && parts.length >= 2) { + if (parts[1] === "auth") { + const token = parsed.searchParams.get("token"); + if (token) { + lceOnlineService.loginWithTokenAndFetchAccount(token); + setActiveView("lceonline"); + return; + } + } + if (parts[1] === "addfriend") { + const username = parsed.searchParams.get("username"); + if (username) { + setActiveView("lceonline"); + setAddFriendTarget(username); + return; + } } } diff --git a/src/services/LceOnlineService.ts b/src/services/LceOnlineService.ts index 96fb2a9..85d0748 100644 --- a/src/services/LceOnlineService.ts +++ b/src/services/LceOnlineService.ts @@ -20,11 +20,22 @@ export interface FriendRequest { export class LceOnlineService { private _session: SessionData | null = null; private baseUrl: string = SOCIAL_BASE_URL; - + private _listeners: Array<() => void> = []; constructor() { this.loadSession(); } + onSessionChange(listener: () => void): () => void { + this._listeners.push(listener); + return () => { + this._listeners = this._listeners.filter((l) => l !== listener); + }; + } + + private _notify() { + for (const l of this._listeners) l(); + } + get signedIn(): boolean { return this._session !== null; } @@ -49,6 +60,7 @@ export class LceOnlineService { logoutLocal(): void { this._session = null; this.saveSession(); + this._notify(); } async login(username: string, password: string): Promise { @@ -63,7 +75,7 @@ export class LceOnlineService { throw new Error(text || "Login failed"); } const token = text.split(":")[1]; - this.loginWithToken(token, username); + await this.loginWithTokenAndFetchAccount(token); } async register(username: string, password: string): Promise { @@ -78,15 +90,37 @@ export class LceOnlineService { throw new Error(text || "Registration failed"); } const token = text.split(":")[1]; - this.loginWithToken(token, username); + await this.loginWithTokenAndFetchAccount(token); } - loginWithToken(token: string, username: string) { + loginWithToken(token: string, username?: string) { + const name = username || "Player"; this._session = { accessToken: token, - account: { username, displayName: username }, + account: { username: name, displayName: name }, }; this.saveSession(); + this._notify(); + } + + async loginWithTokenAndFetchAccount(token: string): Promise { + this._session = { + accessToken: token, + account: { username: "Player", displayName: "Player" }, + }; + this.saveSession(); + this._notify(); + try { + const raw: string = await this.request("POST", "/accountinfo"); + if (typeof raw === "string" && raw.startsWith("-")) { + const username = raw.slice(1); + this._session!.account = { username, displayName: username }; + this.saveSession(); + this._notify(); + } + } catch (e) { + console.warn("Failed to fetch account info", e); + } } private loadSession() { @@ -233,7 +267,13 @@ export class LceOnlineService { } } - async getInvites(): Promise> { + async getInvites(): Promise< + Array<{ + inviteid: string; + from: { uuid: string; username: string }; + sessionid: string; + }> + > { const res = await this.request("GET", "/getinvites", null); return Array.isArray(res) ? res : []; }