Files
Starlight_Lancher/apps/app-frontend/src/helpers/instance-player.ts
T
Disy e4fa234df1
Axolotl desktop CI / guardrails (push) Has been cancelled
Axolotl desktop CI / desktop (macos-latest) (push) Has been cancelled
Axolotl desktop CI / desktop (ubuntu-latest) (push) Has been cancelled
Axolotl desktop CI / desktop (windows-latest) (push) Has been cancelled
Axolotl desktop CI / website (push) Has been cancelled
Repository checks / typos (push) Has been cancelled
Repository checks / tombi (push) Has been cancelled
Rust checks / shear (push) Has been cancelled
Sync source to CNB / Sync Git ref (push) Has been cancelled
fix: 修复皮肤站登录后侧栏玩家选择器未立即显示
侧栏直接合并皮肤站角色与已保存账户并按 UUID 去重,无需先启动实例。
选择角色时才进行认证并保存默认账户,认证失败时保留原选择并允许重试。
移除逐个预认证角色的流程,修复无皮肤玩家的默认头像引用异常。

补充组件回归测试,覆盖首次登录、默认玩家切换、失败重试、退出登录和无皮肤角色。
验证:14 项相关测试及 ESLint 通过,前端生产构建通过。
全量检查仍有既有的设置搜索、升级结果组件测试失败及类型错误。
2026-09-24 14:15:00 +08:00

117 lines
3.7 KiB
TypeScript

import { invoke } from '@tauri-apps/api/core'
import {
requestSkinSiteDownloadToken,
skinSiteStatus,
skinSiteUser,
waitForSkinSiteSession,
} from '../composables/skin-site-session.ts'
import { isOfflineMode } from '../composables/useNetworkStatus.ts'
import { users } from './auth.js'
export { waitForSkinSiteSession } from '../composables/skin-site-session.ts'
export type InstancePlayer = {
id: string
name: string
account_type: 'microsoft' | 'yggdrasil' | 'offline'
skin_site_user?: string | null
}
export type PlayerChoice = InstancePlayer & { head?: string }
/**
* Thrown when the player picker is dismissed because the user navigated to the
* skin-site login page. Callers should treat this as a silent launch abort
* (the user will pick a player next time), not as a real failure.
*/
export class PlayerSelectionNavigatedAwayError extends Error {
constructor() {
super('已跳转至皮肤站登录,请登录后重新启动。')
this.name = 'PlayerSelectionNavigatedAwayError'
}
}
type Picker = (instanceId: string, locked: InstancePlayer | null) => Promise<InstancePlayer>
let picker: Picker | undefined
const preparing = new Map<string, Promise<void>>()
const listeners = new Set<(instanceId: string, player: InstancePlayer) => void>()
export function onInstancePlayerChanged(
listener: (instanceId: string, player: InstancePlayer) => void,
) {
listeners.add(listener)
return () => {
listeners.delete(listener)
}
}
export function registerInstancePlayerPicker(value: Picker) {
picker = value
return () => {
if (picker === value) picker = undefined
}
}
export const getInstancePlayer = (instanceId: string) =>
invoke<InstancePlayer | null>('plugin:auth|get_instance_player', { instanceId })
export async function authenticateInstancePlayer(player: InstancePlayer) {
if (!player.skin_site_user) return
if (skinSiteStatus.value !== 'signed-in' || skinSiteUser.value?.uuid !== player.skin_site_user) {
throw new Error(`请登录实例玩家 ${player.name} 所属的皮肤站账号。`)
}
const token = await requestSkinSiteDownloadToken()
await invoke('plugin:auth|login_skin_site_player', {
token,
playerId: player.id,
userId: player.skin_site_user,
})
if (skinSiteUser.value?.uuid !== player.skin_site_user || skinSiteStatus.value !== 'signed-in') {
throw new Error('皮肤站账号已变化,请重试。')
}
}
export async function saveInstancePlayer(instanceId: string, player: InstancePlayer) {
await authenticateInstancePlayer(player)
await invoke('plugin:auth|set_instance_player', { instanceId, player })
for (const listener of listeners) listener(instanceId, player)
}
export async function chooseInstancePlayer(
instanceId: string,
locked: InstancePlayer | null = null,
) {
if (!picker) throw new Error('玩家选择界面尚未就绪,请稍后重试。')
return picker(instanceId, locked)
}
export function prepareInstancePlayer(instanceId: string): Promise<void> {
if (isOfflineMode()) return Promise.resolve()
const current = preparing.get(instanceId)
if (current) return current
const task = (async () => {
const saved = await getInstancePlayer(instanceId)
if (saved && !saved.skin_site_user) {
const available = await users()
if (
available.some(
(account) =>
account.profile.id === saved.id && account.account_type === saved.account_type,
)
)
return
}
if (saved?.skin_site_user) await waitForSkinSiteSession()
if (
saved?.skin_site_user &&
skinSiteUser.value?.uuid === saved.skin_site_user &&
skinSiteStatus.value === 'signed-in'
) {
await authenticateInstancePlayer(saved)
return
}
await chooseInstancePlayer(instanceId, saved)
})().finally(() => {
preparing.delete(instanceId)
})
preparing.set(instanceId, task)
return task
}