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
侧栏直接合并皮肤站角色与已保存账户并按 UUID 去重,无需先启动实例。 选择角色时才进行认证并保存默认账户,认证失败时保留原选择并允许重试。 移除逐个预认证角色的流程,修复无皮肤玩家的默认头像引用异常。 补充组件回归测试,覆盖首次登录、默认玩家切换、失败重试、退出登录和无皮肤角色。 验证:14 项相关测试及 ESLint 通过,前端生产构建通过。 全量检查仍有既有的设置搜索、升级结果组件测试失败及类型错误。
117 lines
3.7 KiB
TypeScript
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
|
|
}
|