Launcher: panel accounts via authlib-injector, skin & cape editor

Microsoft sign-in is gone. Server accounts now receive Yggdrasil tokens
from the panel and launch with authlib-injector pointed at the panel's
auth server, so online-mode servers verify players against it.

- Download authlib-injector from the panel mirror (fallback: official),
  SHA-256 verified and cached, with prefetched metadata
- New Skin & cape settings tab: upload, arm style, reset, cape picker
  with front/back previews
- Avatars render from the panel's head endpoint

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ARcGWxLx21FwXJ3yfGriS
This commit is contained in:
Claude committed 2026-09-28 08:58:27 +00:00
1 parent 99b45141fc
commit 77a15cab8b
18 files changed
+454 -208

No files matched your search

+27 -7
View File
@@ -1,7 +1,7 @@
// In-browser stand-in for the Rust backend, used by `npm run dev` outside
// Tauri and for UI screenshots. Scenario via ?mock=setup|login|main.
import { mockEmit } from './tauri';
import type { Account, Bootstrap, Manifest, Settings } from './types';
import type { Account, Bootstrap, Manifest, PlayerProfile, Settings } from './types';
const scenario = new URLSearchParams(location.search).get('mock') ?? 'main';
@@ -37,7 +37,7 @@ const manifest: Manifest = {
api_version: 1,
panel_version: '0.1.0',
user: null,
auth: { panel_accounts: true, registration: 'approval', microsoft: true, microsoft_client_id: 'demo', offline_local: true },
auth: { panel_accounts: true, registration: 'approval', offline_local: true, yggdrasil_url: 'https://panel.scopenet.gg/api/yggdrasil' },
branding: {
name: 'SCOPENET',
tagline: 'Your worlds, one click away.',
@@ -76,6 +76,18 @@ const accounts: Account[] = scenario === 'main' || scenario === 'progress' ? [
let active: string | null = accounts[0]?.id ?? null;
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
const profile: PlayerProfile = {
uuid: 'b50ad385-829d-3141-a216-7e7d7539ba7f',
name: 'Notch',
skin_url: null,
skin_model: 'classic',
cape: null,
available_capes: [
{ id: 1, name: 'Founder', url: '' },
{ id: 2, name: 'Season 4', url: '' },
],
};
let running = false;
async function fakeLaunch(id: string) {
@@ -134,11 +146,19 @@ export async function mockInvoke(cmd: string, args: Record<string, any>): Promis
}
case 'register_panel':
return { account: null, pending: true };
case 'ms_start':
return { user_code: 'F7K2-QX9M', verification_uri: 'https://microsoft.com/link', expires_in: 900, interval: 5, message: '' };
case 'ms_finish':
await sleep(600_000);
return null;
case 'account_profile':
case 'set_cape':
case 'set_skin_model':
if (cmd === 'set_cape') profile.cape = profile.available_capes.find((c) => c.id === args.capeId) ?? null;
if (cmd === 'set_skin_model') profile.skin_model = args.model;
return structuredClone(profile);
case 'upload_skin':
profile.skin_url = 'data:image/png;base64,' + args.data;
profile.skin_model = args.model;
return structuredClone(profile);
case 'delete_skin':
profile.skin_url = null;
return structuredClone(profile);
case 'select_account':
active = args.id;
return null;
+2
View File
@@ -39,6 +39,8 @@ export const app = $state({
update: null as UpdateInfo | null,
addAccount: false,
instanceSettings: null as string | null,
/** Bumped after a skin change so avatars reload. */
skinVersion: 0,
toasts: [] as { id: number; text: string; kind: 'ok' | 'error' | 'info' }[],
});
+4 -4
View File
@@ -43,8 +43,7 @@ export interface Branding {
export interface AuthConfig {
panel_accounts: boolean;
registration: 'closed' | 'open' | 'approval';
microsoft: boolean;
microsoft_client_id: string | null;
yggdrasil_url: string | null;
offline_local: boolean;
}
@@ -57,7 +56,7 @@ export interface Manifest {
user: { id: number; username: string; uuid: string; role: string; groups: string[] } | null;
}
export interface Account { id: string; kind: 'panel' | 'microsoft' | 'offline'; username: string; uuid: string; panel_url: string | null; role: string | null }
export interface Account { id: string; kind: 'panel' | 'offline'; username: string; uuid: string; panel_url: string | null; role: string | null }
export type Gc = 'default' | 'g1' | 'zgc';
@@ -108,5 +107,6 @@ export type ProgressEvent =
| { type: 'log'; line: string };
export interface ServerStatus { online: boolean; version: string; players_online: number; players_max: number; sample: string[]; motd: string; favicon: string | null; latency_ms: number }
export interface DeviceCode { user_code: string; verification_uri: string; expires_in: number; interval: number; message: string }
export interface CapeInfo { id: number; name: string; url: string }
export interface PlayerProfile { uuid: string; name: string; skin_url: string | null; skin_model: 'classic' | 'slim'; cape: CapeInfo | null; available_capes: CapeInfo[] }
export interface UpdateInfo { version: string; notes: string; url: string; size: number }