fix: preserve desktop session across restarts
This commit is contained in:
@@ -1171,8 +1171,7 @@ if (!hasSingleInstanceLock) {
|
||||
.then(async () => {
|
||||
suppressNativeWindowMenu()
|
||||
registerDesktopProtocolClient()
|
||||
currentWindowMode = 'login'
|
||||
persistWindowMode('login')
|
||||
currentWindowMode = readPersistedWindowMode()
|
||||
initDesktopAppSettings()
|
||||
registerBridgeHandlers()
|
||||
await initSessionRegistry()
|
||||
@@ -1229,14 +1228,8 @@ if (!hasSingleInstanceLock) {
|
||||
quitReleaseInFlight = true
|
||||
event.preventDefault()
|
||||
|
||||
currentWindowMode = 'login'
|
||||
persistWindowMode('login')
|
||||
|
||||
void Promise.race([
|
||||
(async () => {
|
||||
await releaseRuntimeSession()
|
||||
await clearRendererDesktopSessions()
|
||||
})(),
|
||||
releaseRuntimeSession(),
|
||||
new Promise<void>((resolve) => setTimeout(resolve, 1500)),
|
||||
]).finally(() => {
|
||||
app.quit()
|
||||
|
||||
@@ -67,6 +67,17 @@ if (rendererWindowMode === 'main') {
|
||||
})
|
||||
}
|
||||
|
||||
if (rendererWindowMode === 'login') {
|
||||
if (isAuthenticated.value) {
|
||||
syncWindowMode(true)
|
||||
}
|
||||
watch(isAuthenticated, (next) => {
|
||||
if (next) {
|
||||
syncWindowMode(true)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (rendererWindowMode !== 'login') {
|
||||
void syncRuntimeSession().catch((err) => {
|
||||
|
||||
@@ -68,11 +68,6 @@ function readStoredSession(): DesktopSession | null {
|
||||
}
|
||||
|
||||
try {
|
||||
if (shouldDiscardStoredSessionForWindow()) {
|
||||
window.localStorage.removeItem(sessionStorageKey)
|
||||
return null
|
||||
}
|
||||
|
||||
const raw = window.localStorage.getItem(sessionStorageKey)
|
||||
if (!raw) {
|
||||
return null
|
||||
@@ -83,10 +78,6 @@ function readStoredSession(): DesktopSession | null {
|
||||
}
|
||||
}
|
||||
|
||||
function shouldDiscardStoredSessionForWindow(): boolean {
|
||||
return new URLSearchParams(window.location.search).get('window') === 'login'
|
||||
}
|
||||
|
||||
function normalizeStoredSession(value: unknown): DesktopSession | null {
|
||||
if (!value || typeof value !== 'object') {
|
||||
return null
|
||||
@@ -166,6 +157,7 @@ function createAuthenticatedClient() {
|
||||
return createApiClient({
|
||||
baseURL: apiBaseURL.value,
|
||||
timeoutMs: 15000,
|
||||
clearTokensOnUnauthorized: false,
|
||||
auth: {
|
||||
getAccessToken: () => session.value?.accessToken ?? null,
|
||||
getRefreshToken: () => session.value?.refreshToken ?? null,
|
||||
@@ -181,7 +173,18 @@ function createAuthenticatedClient() {
|
||||
refreshToken: tokens.refreshToken,
|
||||
})
|
||||
},
|
||||
clearTokens: () => persistSession(null),
|
||||
clearTokens: () => {
|
||||
if (!session.value) {
|
||||
return
|
||||
}
|
||||
|
||||
persistSession({
|
||||
...session.value,
|
||||
accessToken: null,
|
||||
accessTokenExpiresAt: null,
|
||||
refreshToken: null,
|
||||
})
|
||||
},
|
||||
async refresh(refreshToken: string): Promise<AuthTokens> {
|
||||
const next = await createPublicClient().post<RefreshResponse, { refresh_token: string }>(
|
||||
'/api/auth/refresh',
|
||||
@@ -239,6 +242,14 @@ function expiresAtMs(value: number | null | undefined): number | null {
|
||||
return value < 10_000_000_000 ? value * 1000 : value
|
||||
}
|
||||
|
||||
function hasUsableStoredSession(value: DesktopSession | null): boolean {
|
||||
return Boolean(
|
||||
value?.clientToken &&
|
||||
value.desktopClient &&
|
||||
(value.mode === 'authenticated' || value.mode === 'preview'),
|
||||
)
|
||||
}
|
||||
|
||||
function isRenewDue(value: number | null | undefined, renewWindowMs: number): boolean {
|
||||
const expiresAt = expiresAtMs(value)
|
||||
if (!expiresAt) {
|
||||
@@ -247,6 +258,17 @@ function isRenewDue(value: number | null | undefined, renewWindowMs: number): bo
|
||||
return expiresAt - Date.now() <= renewWindowMs
|
||||
}
|
||||
|
||||
function isApiAuthExpiredError(err: unknown): boolean {
|
||||
return err instanceof ApiClientError && err.status === 401
|
||||
}
|
||||
|
||||
function isTransientRenewalError(err: unknown): boolean {
|
||||
if (err instanceof ApiClientError) {
|
||||
return !err.status || err.status >= 500 || err.message === 'request_timeout'
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
async function refreshLoginTokens(current: DesktopSession): Promise<DesktopSession> {
|
||||
if (!current.refreshToken) {
|
||||
throw new Error('missing_refresh_token')
|
||||
@@ -302,6 +324,16 @@ async function forceLogoutAfterRenewFailure(err: unknown): Promise<void> {
|
||||
await transitionWindowForLogout()
|
||||
}
|
||||
|
||||
function keepSessionAfterRenewFailure(err: unknown): void {
|
||||
console.warn('[desktop-session] token renewal deferred', err)
|
||||
error.value = isTransientRenewalError(err)
|
||||
? '登录状态暂时无法刷新,将保持当前客户端登录状态并自动重试'
|
||||
: '客户端登录状态刷新失败,将保持当前会话并自动重试'
|
||||
void syncRuntimeSessionToMain().catch((syncError) => {
|
||||
logRuntimeSessionSyncFailure('renewal-deferred', syncError)
|
||||
})
|
||||
}
|
||||
|
||||
async function renewAuthenticatedSession(options: { force?: boolean } = {}): Promise<void> {
|
||||
const current = session.value
|
||||
if (current?.mode !== 'authenticated') {
|
||||
@@ -325,7 +357,15 @@ async function renewAuthenticatedSession(options: { force?: boolean } = {}): Pro
|
||||
}
|
||||
|
||||
if (needsAccessRefresh) {
|
||||
next = await refreshLoginTokens(next)
|
||||
try {
|
||||
next = await refreshLoginTokens(next)
|
||||
} catch (err) {
|
||||
if (isApiAuthExpiredError(err)) {
|
||||
throw err
|
||||
}
|
||||
keepSessionAfterRenewFailure(err)
|
||||
return
|
||||
}
|
||||
if (session.value !== expectedSession) {
|
||||
return
|
||||
}
|
||||
@@ -334,7 +374,15 @@ async function renewAuthenticatedSession(options: { force?: boolean } = {}): Pro
|
||||
}
|
||||
|
||||
if (needsClientRotate) {
|
||||
next = await rotateRuntimeClientToken(next)
|
||||
try {
|
||||
next = await rotateRuntimeClientToken(next)
|
||||
} catch (err) {
|
||||
if (isApiAuthExpiredError(err)) {
|
||||
throw err
|
||||
}
|
||||
keepSessionAfterRenewFailure(err)
|
||||
return
|
||||
}
|
||||
if (session.value !== expectedSession) {
|
||||
return
|
||||
}
|
||||
@@ -780,7 +828,7 @@ export function useDesktopSession() {
|
||||
apiBaseURL: readonly(apiBaseURL),
|
||||
pending: readonly(pending),
|
||||
error: readonly(error),
|
||||
isAuthenticated: computed(() => Boolean(session.value?.clientToken)),
|
||||
isAuthenticated: computed(() => hasUsableStoredSession(session.value)),
|
||||
isPreviewMode: computed(() => session.value?.mode === 'preview'),
|
||||
setApiBaseURL: persistApiBaseURL,
|
||||
syncRuntimeSession: syncRuntimeSessionToMain,
|
||||
|
||||
Reference in New Issue
Block a user