feat: implement desktop authorization recovery handling and session management improvements
Desktop Client Build / Resolve Build Metadata (push) Successful in 24s
Desktop Client Build / Build Desktop Client (push) Successful in 21m30s
Desktop Client Build / Publish Client Artifacts to NAS (push) Successful in 26s

This commit is contained in:
2026-06-24 11:00:19 +08:00
parent 0afd082d96
commit 6b60438f25
6 changed files with 317 additions and 19 deletions
@@ -53,6 +53,7 @@ const error = shallowRef<string | null>(null)
let loginPromise: Promise<void> | null = null
let logoutPromise: Promise<void> | null = null
let renewPromise: Promise<void> | null = null
let recoverDesktopClientPromise: Promise<void> | null = null
let tokenRenewTimer: ReturnType<typeof setInterval> | null = null
function logRuntimeSessionSyncFailure(reason: string, err: unknown): void {
@@ -300,6 +301,20 @@ async function rotateRuntimeClientToken(current: DesktopSession): Promise<Deskto
return applyRotatedDesktopClient(current, rotated)
}
async function refreshDesktopClientRegistration(current: DesktopSession): Promise<DesktopSession> {
if (!current.user) {
throw new Error('missing_desktop_user')
}
const registered = await registerDesktopClient(current.user)
return {
...current,
clientToken: registered.client_token,
clientTokenExpiresAt: registered.expires_at,
desktopClient: registered.client,
}
}
function applyRotatedDesktopClient(
current: DesktopSession,
rotated: DesktopClientRotateResponse,
@@ -317,7 +332,7 @@ async function forceLogoutAfterRenewFailure(err: unknown): Promise<void> {
error.value = '登录状态已过期,请重新登录'
persistSession(null)
try {
await window.desktopBridge?.app?.releaseRuntimeSession?.(true)
await window.desktopBridge?.app?.releaseRuntimeSession?.(false, { skipRemote: true })
} catch (releaseError) {
console.warn('[desktop-session] release after renewal failure failed', releaseError)
}
@@ -400,6 +415,54 @@ async function renewAuthenticatedSession(options: { force?: boolean } = {}): Pro
return renewPromise
}
async function recoverDesktopClientSession(): Promise<void> {
const current = session.value
if (current?.mode !== 'authenticated') {
await forceLogoutAfterRenewFailure(new Error('desktop_auth_recovery_requires_login'))
return
}
if (recoverDesktopClientPromise) {
return recoverDesktopClientPromise
}
recoverDesktopClientPromise = (async () => {
let next = current
let expectedSession = current
try {
next = await refreshLoginTokens(next)
} catch (err) {
if (isApiAuthExpiredError(err)) {
throw err
}
keepSessionAfterRenewFailure(err)
throw err
}
if (session.value !== expectedSession) {
return
}
persistSession(next)
expectedSession = next
next = await refreshDesktopClientRegistration(next)
if (session.value !== expectedSession) {
return
}
persistSession(next)
})()
.catch(async (err) => {
await forceLogoutAfterRenewFailure(err)
throw err
})
.finally(() => {
recoverDesktopClientPromise = null
})
return recoverDesktopClientPromise
}
function ensureTokenRenewTimer(): void {
if (typeof window === 'undefined' || tokenRenewTimer) {
return
@@ -820,6 +883,15 @@ if (typeof window !== 'undefined') {
void syncStoredDesktopClientDeviceInfo()
ensureTokenRenewTimer()
void renewAuthenticatedSession()
window.desktopBridge?.app?.onAuthRecoveryRequested?.((event) => {
void recoverDesktopClientSession().catch((err) => {
console.warn('[desktop-session] desktop auth recovery failed', {
requestId: event.requestId,
message: err instanceof Error ? err.message : String(err),
})
})
})
}
export function useDesktopSession() {
@@ -833,6 +905,7 @@ export function useDesktopSession() {
setApiBaseURL: persistApiBaseURL,
syncRuntimeSession: syncRuntimeSessionToMain,
renewSession: renewAuthenticatedSession,
recoverDesktopClientSession,
login,
logout,
enterPreview,
+14 -1
View File
@@ -9,6 +9,7 @@ import type {
DesktopClientUpdateResult,
DesktopPublishTaskListResponse,
DesktopRuntimeSessionSyncRequest,
DesktopTaskInfo,
ListDesktopPublishTasksParams,
} from '@geo/shared-types'
import type { DesktopObservedRequest } from '../shared/network-debug'
@@ -75,6 +76,17 @@ declare global {
secure: boolean
}
interface DesktopAuthRecoveryRequest {
reason: 'desktop-client-token-unauthorized'
method: string
path: string
status: number
code?: number
message: string
requestId: string
at: number
}
interface Window {
desktopBridge: {
app: {
@@ -132,7 +144,7 @@ declare global {
cleanStorage(): Promise<DesktopStorageCleanupResult>
syncRuntimeSession(session: DesktopRuntimeSessionSyncRequest | null): Promise<null>
rotateRuntimeClientToken(): Promise<DesktopClientRotateResponse>
releaseRuntimeSession(revoke?: boolean): Promise<null>
releaseRuntimeSession(revoke?: boolean, options?: { skipRemote?: boolean }): Promise<null>
setWindowMode(
mode: 'login' | 'main',
request?: {
@@ -140,6 +152,7 @@ declare global {
animate?: boolean
},
): Promise<null>
onAuthRecoveryRequested(listener: (event: DesktopAuthRecoveryRequest) => void): () => void
onRuntimeInvalidated(
listener: (event: {
reason: