feat: implement desktop authorization recovery handling and session management improvements
This commit is contained in:
@@ -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
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user