fix: OneClient Skin Manager Crash when Invalid Account (#404)

* fix: oneclient skin manager crash when invalid account

* fix: formatting
This commit is contained in:
Jacob 2025-11-11 12:44:55 +00:00 • committed by GitHub
parent 878f7ed5ab
commit c07d4637d3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 41 additions and 16 deletions

View file

@ -255,11 +255,8 @@ export type VersionType =
*/
"old_beta"
const ARGS_MAP = { 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}', 'oneclient':'{"openDevTools":[],"getClustersGroupedByMajor":[],"getBundlesFor":["cluster_id"]}', 'core':'{"createCluster":["options"],"writeSettings":["setting"],"getPackageBody":["provider","body"],"getUser":["uuid"],"getMultiplePackages":["provider","slugs"],"removeCape":["access_token"],"openMsaLogin":[],"installModpack":["modpack","cluster_id"],"fetchLoggedInProfile":["access_token"],"getLogByName":["id","name"],"getUsers":[],"uploadSkinBytes":["access_token","skin_data","image_format","skin_variant"],"killProcess":["pid"],"getClusters":[],"fetchMinecraftProfile":["uuid"],"getScreenshots":["id"],"getWorlds":["id"],"readSettings":[],"launchCluster":["id","uuid"],"getLoadersForVersion":["mc_version"],"getGameVersions":[],"getUsersFromAuthor":["provider","author"],"getRunningProcessesByClusterId":["cluster_id"],"changeCape":["access_token","cape_uuid"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"getClusterById":["id"],"setDefaultUser":["uuid"],"getGlobalProfile":[],"updateClusterById":["id","request"],"removeCluster":["id"],"getRunningProcesses":[],"isClusterRunning":["cluster_id"],"updateClusterProfile":["name","profile"],"removeUser":["uuid"],"getLogs":["id"],"getDefaultUser":["fallback"],"searchPackages":["provider","query"],"getProfileOrDefault":["name"],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"open":["input"],"convertUsernameUUID":["username_uuid"],"createSettingsProfile":["name"],"changeSkin":["access_token","skin_url","skin_variant"],"getPackage":["provider","slug"]}', 'events':'{"process":["event"],"ingress":["event"],"message":["event"]}' }
export type Router = { 'oneclient': { openDevTools: () => Promise<void>,
getClustersGroupedByMajor: () => Promise<Partial<{ [key in number]: ClusterModel[] }>>,
getBundlesFor: (clusterId: number) => Promise<ModpackArchive[]> },
'core': { getClusters: () => Promise<ClusterModel[]>,
const ARGS_MAP = { 'oneclient':'{"getClustersGroupedByMajor":[],"getBundlesFor":["cluster_id"],"openDevTools":[]}', 'core':'{"uploadSkinBytes":["access_token","skin_data","image_format","skin_variant"],"removeCape":["access_token"],"getGameVersions":[],"installModpack":["modpack","cluster_id"],"createSettingsProfile":["name"],"getPackageBody":["provider","body"],"getLogByName":["id","name"],"getScreenshots":["id"],"getWorlds":["id"],"getMultiplePackages":["provider","slugs"],"changeCape":["access_token","cape_uuid"],"convertUsernameUUID":["username_uuid"],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"fetchMinecraftProfile":["uuid"],"getPackage":["provider","slug"],"getUsersFromAuthor":["provider","author"],"createCluster":["options"],"getGlobalProfile":[],"getRunningProcesses":[],"setDefaultUser":["uuid"],"changeSkin":["access_token","skin_url","skin_variant"],"writeSettings":["setting"],"updateClusterById":["id","request"],"open":["input"],"readSettings":[],"getRunningProcessesByClusterId":["cluster_id"],"isClusterRunning":["cluster_id"],"getLoadersForVersion":["mc_version"],"removeCluster":["id"],"launchCluster":["id","uuid"],"getProfileOrDefault":["name"],"updateClusterProfile":["name","profile"],"getLogs":["id"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"getUser":["uuid"],"searchPackages":["provider","query"],"killProcess":["pid"],"getClusterById":["id"],"getUsers":[],"getClusters":[],"openMsaLogin":[],"removeUser":["uuid"],"fetchLoggedInProfile":["access_token"],"getDefaultUser":["fallback"]}', 'folders':'{"openCluster":["folder_name"],"fromCluster":["folder_name"]}', 'events':'{"message":["event"],"process":["event"],"ingress":["event"]}' }
export type Router = { 'core': { getClusters: () => Promise<ClusterModel[]>,
getClusterById: (id: number) => Promise<ClusterModel | null>,
removeCluster: (id: number) => Promise<null>,
createCluster: (options: CreateCluster) => Promise<ClusterModel>,
@ -307,7 +304,10 @@ open: (input: string) => Promise<null> },
openCluster: (folderName: string) => Promise<null> },
'events': { ingress: (event: IngressPayload) => Promise<void>,
message: (event: MessagePayload) => Promise<void>,
process: (event: ProcessPayload) => Promise<void> } };
process: (event: ProcessPayload) => Promise<void> },
'oneclient': { openDevTools: () => Promise<void>,
getClustersGroupedByMajor: () => Promise<Partial<{ [key in number]: ClusterModel[] }>>,
getBundlesFor: (clusterId: number) => Promise<ModpackArchive[]> } };
export type { InferCommandOutput }

View file

@ -2,39 +2,49 @@ import type { MinecraftCredentials } from '@/bindings.gen';
import { LoaderSuspense } from '@/components';
import { bindings } from '@/main';
import { createFileRoute, Outlet } from '@tanstack/react-router';
import { MissingAccountData } from './skins';
export interface AccountsRouteSearchParams {
profile: MinecraftCredentials;
profile: MinecraftCredentials | undefined;
search: boolean;
}
export const Route = createFileRoute('/app/account')({
component: RouteComponent,
validateSearch: (search): AccountsRouteSearchParams => {
return {
profile: search.profile as MinecraftCredentials,
profile: search.profile as MinecraftCredentials | undefined,
search: search.profile === undefined,
};
},
async beforeLoad({ context, search }) {
// TODO instead replace this with a refresh access_token function. Waiting on binding from @LynithDev
const { profile, search: validSearch } = search;
if (!profile)
return { profileData: null, profile: null, validSearch };
const query = context.queryClient.ensureQueryData({
queryKey: ['fetchLoggedInProfile', search.profile.access_token],
queryFn: () => bindings.core.fetchLoggedInProfile(search.profile.access_token),
queryKey: ['fetchLoggedInProfile', profile.access_token],
queryFn: () => bindings.core.fetchLoggedInProfile(profile.access_token),
});
const profileData = await query;
return {
profileData,
profile: search.profile,
profile,
validSearch,
};
},
});
function RouteComponent() {
const { profileData, validSearch } = Route.useRouteContext();
return (
<>
<LoaderSuspense spinner={{ size: 'large' }}>
<Outlet />
{profileData === null ? <MissingAccountData validSearch={validSearch} /> : <Outlet />}
</LoaderSuspense>
</>
);
}

View file

@ -102,26 +102,36 @@ function useSkinHistory() {
return [skins, setSkins, loaded] as const;
}
export function MissingAccountData({ validSearch }: { validSearch: boolean }) {
return (
<SheetPage headerLarge={<></>} headerSmall={<></>}>
<h1>{validSearch ? 'Please select an account before going to the skins page' : 'Missing Profile auth. Please log out and log back in'}</h1>
</SheetPage>
);
}
function RouteComponent() {
const { profileData, profile, queryClient } = Route.useRouteContext();
const { profileData, profile, queryClient, validSearch } = Route.useRouteContext();
const [capes, setCapes] = useState<Array<Cape>>([]);
const [selectedCape, setSelectedCape] = useState<string>('');
const [shouldShowElytra, setShouldShowElytra] = useState<boolean>(false);
useEffect(() => {
if (!profileData)
return;
setCapes([{ url: '', id: '' }, ...profileData.capes.map(cape => ({ url: cape.url, id: cape.id }))]);
}, []);
const [skins, setSkins, loaded] = useSkinHistory();
const [selectedSkin, setSelectedSkin] = useState<Skin>({ skin_url: getSkinUrl(null), is_slim: false });
const skinData: Skin = { is_slim: profileData.skins[0].variant === 'slim', skin_url: getSkinUrl(profileData.skins[0].url) };
const skinData: Skin = { is_slim: profileData?.skins[0].variant === 'slim', skin_url: getSkinUrl(profileData?.skins[0].url) };
useEffect(() => {
if (!loaded)
return;
setSkins(prev => [...prev, skinData]); // ✅ always merges with latest
setSkins(prev => [...prev, skinData]);
setSelectedSkin(skinData);
}, [loaded]);
@ -155,6 +165,8 @@ function RouteComponent() {
const saveSkinToAccount = async () => {
try {
if (!profile)
return;
await bindings.core.changeSkin(profile.access_token, selectedSkin.skin_url, selectedSkin.is_slim ? 'slim' : 'classic');
if (selectedCape === '') {
await bindings.core.removeCape(profile.access_token);
@ -200,6 +212,9 @@ function RouteComponent() {
setAnimationName(data.name);
};
if (profileData === null)
return <MissingAccountData validSearch={validSearch} />;
return (
<SheetPage
headerLarge={(