feat(desktop): implement client token rotation and session management enhancements
This commit is contained in:
@@ -1,7 +1,11 @@
|
||||
import { readFileSync, writeFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
|
||||
import type { DesktopAccountInfo, DesktopRuntimeSessionSyncRequest } from '@geo/shared-types'
|
||||
import type {
|
||||
DesktopAccountInfo,
|
||||
DesktopClientRotateResponse,
|
||||
DesktopRuntimeSessionSyncRequest,
|
||||
} from '@geo/shared-types'
|
||||
import { isAIPlatformId } from '@geo/shared-types'
|
||||
import { shell } from 'electron'
|
||||
import type {
|
||||
@@ -38,12 +42,13 @@ import { registerRendererDevtoolsProxyTarget } from './renderer-devtools-proxy'
|
||||
import {
|
||||
noteRuntimeAccountBound,
|
||||
refreshRuntimeAccounts,
|
||||
getRuntimeControllerSnapshot,
|
||||
releaseRuntimeSession,
|
||||
requestRuntimeAccountProbe,
|
||||
syncRuntimeSession,
|
||||
unbindRuntimeAccount,
|
||||
} from './runtime-controller'
|
||||
import { onRuntimeInvalidated } from './runtime-events'
|
||||
import { onRuntimeAuthExpired, onRuntimeInvalidated } from './runtime-events'
|
||||
import { createRuntimeAccountSnapshot, createRuntimeSnapshot } from './runtime-snapshot'
|
||||
import { initScheduler } from './scheduler'
|
||||
import { initSessionRegistry } from './session-registry'
|
||||
@@ -52,6 +57,7 @@ import {
|
||||
initTransport,
|
||||
listDesktopPublishTasks,
|
||||
retryDesktopPublishTask,
|
||||
rotateDesktopClient,
|
||||
} from './transport/api-client'
|
||||
import { initTray, showTrayBalloon } from './tray'
|
||||
import { STANDARD_USER_AGENT } from './user-agent'
|
||||
@@ -120,6 +126,7 @@ let mainWindowCreatePromise: Promise<ElectronBrowserWindow> | null = null
|
||||
let loginWindowCreatePromise: Promise<ElectronBrowserWindow> | null = null
|
||||
let settingsWindowCreatePromise: Promise<ElectronBrowserWindow> | null = null
|
||||
let windowModeSwitchQueue: Promise<void> = Promise.resolve()
|
||||
let clientTokenRotatePromise: Promise<DesktopClientRotateResponse> | null = null
|
||||
let canHandleDesktopDeepLinks = false
|
||||
const pendingDesktopDeepLinks: string[] = []
|
||||
const forceCloseWindows = new WeakSet<ElectronBrowserWindow>()
|
||||
@@ -358,6 +365,32 @@ function captureRuntimeAccountSnapshot(accountId: string) {
|
||||
return createRuntimeAccountSnapshot(accountId)
|
||||
}
|
||||
|
||||
async function clearRendererDesktopSession(window: ElectronBrowserWindow | null): Promise<void> {
|
||||
if (!window || window.isDestroyed()) {
|
||||
return
|
||||
}
|
||||
|
||||
await window.webContents
|
||||
.executeJavaScript("window.localStorage.removeItem('geo.desktop.session.v1')", true)
|
||||
.catch((error) => {
|
||||
console.warn('[desktop-main] clear renderer desktop session failed', {
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function clearRendererDesktopSessions(): Promise<void> {
|
||||
await Promise.all(
|
||||
BrowserWindow.getAllWindows().map((window) =>
|
||||
clearRendererDesktopSession(window).catch((error) => {
|
||||
console.warn('[desktop-main] clear window desktop session failed', {
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
})
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
async function ensureMainWindow(): Promise<ElectronBrowserWindow> {
|
||||
const existing = currentMainWindow()
|
||||
if (existing) {
|
||||
@@ -491,6 +524,13 @@ function queueWindowModeSwitch(
|
||||
return nextSwitch
|
||||
}
|
||||
|
||||
async function forceLoginWindowForAuthExpired(message: string): Promise<void> {
|
||||
console.warn('[desktop-main] runtime auth expired; switching to login window', { message })
|
||||
syncRuntimeSession(null)
|
||||
await clearRendererDesktopSessions()
|
||||
await queueWindowModeSwitch('login', { source: 'logout', animate: true }, currentMainWindow())
|
||||
}
|
||||
|
||||
async function openSettingsWindow(): Promise<void> {
|
||||
const window = await ensureSettingsWindow()
|
||||
const parent = settingsWindowParent()
|
||||
@@ -890,7 +930,35 @@ function registerBridgeHandlers(): void {
|
||||
return null
|
||||
},
|
||||
)
|
||||
safeHandle('desktop:runtime-session-rotate-client-token', async () => {
|
||||
if (!clientTokenRotatePromise) {
|
||||
clientTokenRotatePromise = rotateDesktopClient()
|
||||
.then((rotated) => {
|
||||
const currentSession = getRuntimeControllerSnapshot().session
|
||||
if (
|
||||
currentSession?.mode === 'authenticated' &&
|
||||
currentSession.desktop_client?.id === rotated.client.id
|
||||
) {
|
||||
syncRuntimeSession({
|
||||
...currentSession,
|
||||
client_token: rotated.client_token,
|
||||
desktop_client: rotated.client,
|
||||
})
|
||||
}
|
||||
return rotated
|
||||
})
|
||||
.finally(() => {
|
||||
clientTokenRotatePromise = null
|
||||
})
|
||||
}
|
||||
return clientTokenRotatePromise
|
||||
})
|
||||
safeHandle('desktop:runtime-session-release', async (_event, revoke?: boolean) => {
|
||||
if (revoke) {
|
||||
currentWindowMode = 'login'
|
||||
persistWindowMode('login')
|
||||
await clearRendererDesktopSessions()
|
||||
}
|
||||
await releaseRuntimeSession({ revoke: Boolean(revoke) })
|
||||
return null
|
||||
})
|
||||
@@ -920,7 +988,8 @@ if (!hasSingleInstanceLock) {
|
||||
.then(async () => {
|
||||
suppressNativeWindowMenu()
|
||||
registerDesktopProtocolClient()
|
||||
currentWindowMode = readPersistedWindowMode()
|
||||
currentWindowMode = 'login'
|
||||
persistWindowMode('login')
|
||||
initDesktopAppSettings()
|
||||
registerBridgeHandlers()
|
||||
await initSessionRegistry()
|
||||
@@ -937,6 +1006,11 @@ if (!hasSingleInstanceLock) {
|
||||
}
|
||||
mainRendererContents.send('desktop:runtime-invalidated', event)
|
||||
})
|
||||
onRuntimeAuthExpired((event) => {
|
||||
void forceLoginWindowForAuthExpired(event.message).catch((error) => {
|
||||
console.error('[desktop-main] auth-expired window transition failed', error)
|
||||
})
|
||||
})
|
||||
await queueWindowModeSwitch(currentWindowMode)
|
||||
initTray(() => {
|
||||
revealActiveWindowSafely('tray')
|
||||
@@ -976,8 +1050,14 @@ if (!hasSingleInstanceLock) {
|
||||
quitReleaseInFlight = true
|
||||
event.preventDefault()
|
||||
|
||||
currentWindowMode = 'login'
|
||||
persistWindowMode('login')
|
||||
|
||||
void Promise.race([
|
||||
releaseRuntimeSession(),
|
||||
(async () => {
|
||||
await clearRendererDesktopSessions()
|
||||
await releaseRuntimeSession()
|
||||
})(),
|
||||
new Promise<void>((resolve) => setTimeout(resolve, 1500)),
|
||||
]).finally(() => {
|
||||
app.quit()
|
||||
|
||||
@@ -84,7 +84,11 @@ import {
|
||||
notePublishTaskLeaseMiss,
|
||||
selectNextPublishTask,
|
||||
} from './publish-scheduler'
|
||||
import { emitRuntimeInvalidated, onRuntimeInvalidated } from './runtime-events'
|
||||
import {
|
||||
emitRuntimeAuthExpired,
|
||||
emitRuntimeInvalidated,
|
||||
onRuntimeInvalidated,
|
||||
} from './runtime-events'
|
||||
import {
|
||||
initScheduler,
|
||||
noteSchedulerAccountsSync,
|
||||
@@ -3277,6 +3281,7 @@ function handleAuthExpired(error: unknown): void {
|
||||
setTransportAuthState('expired')
|
||||
recordActivity('danger', '客户端令牌已过期', message)
|
||||
stopRuntime()
|
||||
emitRuntimeAuthExpired(message)
|
||||
}
|
||||
|
||||
function selectPublishAdapter(platform: string): PublishAdapter | null {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
type RuntimeInvalidationReason = 'account-health' | 'publish-task-lease'
|
||||
|
||||
type RuntimeAuthExpiredListener = (event: {
|
||||
reason: 'client-auth-expired'
|
||||
at: number
|
||||
message: string
|
||||
}) => void
|
||||
|
||||
type RuntimeInvalidationListener = (event: {
|
||||
reason: RuntimeInvalidationReason
|
||||
at: number
|
||||
@@ -8,6 +14,7 @@ type RuntimeInvalidationListener = (event: {
|
||||
}) => void
|
||||
|
||||
const listeners = new Set<RuntimeInvalidationListener>()
|
||||
const authExpiredListeners = new Set<RuntimeAuthExpiredListener>()
|
||||
|
||||
export function emitRuntimeInvalidated(
|
||||
reason: RuntimeInvalidationReason,
|
||||
@@ -37,3 +44,28 @@ export function onRuntimeInvalidated(listener: RuntimeInvalidationListener): ()
|
||||
listeners.delete(listener)
|
||||
}
|
||||
}
|
||||
|
||||
export function emitRuntimeAuthExpired(message: string): void {
|
||||
const event = {
|
||||
reason: 'client-auth-expired',
|
||||
at: Date.now(),
|
||||
message,
|
||||
} as const
|
||||
|
||||
for (const listener of authExpiredListeners) {
|
||||
try {
|
||||
listener(event)
|
||||
} catch (error) {
|
||||
console.warn('[desktop-runtime] auth-expired listener failed', {
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function onRuntimeAuthExpired(listener: RuntimeAuthExpiredListener): () => void {
|
||||
authExpiredListeners.add(listener)
|
||||
return () => {
|
||||
authExpiredListeners.delete(listener)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import type {
|
||||
DesktopArticleContent,
|
||||
DesktopClientHeartbeatRequest,
|
||||
DesktopClientHeartbeatResponse,
|
||||
DesktopClientRotateResponse,
|
||||
DesktopPublishTaskListResponse,
|
||||
DesktopRuntimeSessionSyncRequest,
|
||||
DesktopTaskInfo,
|
||||
@@ -430,6 +431,13 @@ export async function heartbeatDesktopClient(
|
||||
)
|
||||
}
|
||||
|
||||
export async function rotateDesktopClient(): Promise<DesktopClientRotateResponse> {
|
||||
return desktopPost<DesktopClientRotateResponse, Record<string, never>>(
|
||||
'/api/desktop/clients/rotate',
|
||||
{},
|
||||
)
|
||||
}
|
||||
|
||||
export async function offlineDesktopClient(): Promise<void> {
|
||||
await desktopPost<{ server_time: string }, Record<string, never>>(
|
||||
'/api/desktop/clients/offline',
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type {
|
||||
CreatePublishJobResponse,
|
||||
DesktopAccountInfo,
|
||||
DesktopClientRotateResponse,
|
||||
DesktopPublishTaskListResponse,
|
||||
DesktopRuntimeSessionSyncRequest,
|
||||
ListDesktopPublishTasksParams,
|
||||
@@ -140,6 +141,10 @@ const desktopBridge = {
|
||||
ipcRenderer.invoke('desktop:set-app-setting', key, value) as Promise<DesktopAppSettings>,
|
||||
syncRuntimeSession: (session: DesktopRuntimeSessionSyncRequest | null) =>
|
||||
ipcRenderer.invoke('desktop:runtime-session-sync', session) as Promise<null>,
|
||||
rotateRuntimeClientToken: () =>
|
||||
ipcRenderer.invoke(
|
||||
'desktop:runtime-session-rotate-client-token',
|
||||
) as Promise<DesktopClientRotateResponse>,
|
||||
releaseRuntimeSession: (revoke?: boolean) =>
|
||||
ipcRenderer.invoke('desktop:runtime-session-release', revoke) as Promise<null>,
|
||||
setWindowMode: (mode: 'login' | 'main', request?: WindowModeRequest) =>
|
||||
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
DesktopClientInfo,
|
||||
DesktopClientRegisterRequest,
|
||||
DesktopClientRegisterResponse,
|
||||
DesktopClientRotateResponse,
|
||||
DesktopRuntimeSessionSyncRequest,
|
||||
LoginRequest,
|
||||
LoginResponse,
|
||||
@@ -20,6 +21,7 @@ type SessionMode = 'authenticated' | 'preview'
|
||||
interface DesktopSession {
|
||||
mode: SessionMode
|
||||
accessToken: string | null
|
||||
accessTokenExpiresAt: number | null
|
||||
refreshToken: string | null
|
||||
user: UserInfo | null
|
||||
clientToken: string | null
|
||||
@@ -35,6 +37,9 @@ interface ResolvedDeviceInfo {
|
||||
|
||||
const sessionStorageKey = 'geo.desktop.session.v1'
|
||||
const apiBaseURLStorageKey = 'geo.desktop.api-base-url'
|
||||
const tokenRenewCheckMs = 60_000
|
||||
const accessTokenRenewWindowMs = 5 * 60_000
|
||||
const clientTokenRenewWindowMs = 3 * 24 * 60 * 60_000
|
||||
export const DEFAULT_API_BASE_URL =
|
||||
import.meta.env.VITE_DESKTOP_API_BASE_URL ?? 'https://api.shengxintui.com'
|
||||
const normalizedDefaultApiBaseURL = normalizeDesktopApiBaseURL(DEFAULT_API_BASE_URL)
|
||||
@@ -45,6 +50,8 @@ const pending = shallowRef(false)
|
||||
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 tokenRenewTimer: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
function readStoredSession(): DesktopSession | null {
|
||||
if (typeof window === 'undefined') {
|
||||
@@ -52,16 +59,49 @@ function readStoredSession(): DesktopSession | null {
|
||||
}
|
||||
|
||||
try {
|
||||
if (shouldDiscardStoredSessionForWindow()) {
|
||||
window.localStorage.removeItem(sessionStorageKey)
|
||||
return null
|
||||
}
|
||||
|
||||
const raw = window.localStorage.getItem(sessionStorageKey)
|
||||
if (!raw) {
|
||||
return null
|
||||
}
|
||||
return JSON.parse(raw) as DesktopSession
|
||||
return normalizeStoredSession(JSON.parse(raw))
|
||||
} catch {
|
||||
return 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
|
||||
}
|
||||
|
||||
const candidate = value as Partial<DesktopSession>
|
||||
if (candidate.mode !== 'authenticated' && candidate.mode !== 'preview') {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
mode: candidate.mode,
|
||||
accessToken: typeof candidate.accessToken === 'string' ? candidate.accessToken : null,
|
||||
accessTokenExpiresAt:
|
||||
typeof candidate.accessTokenExpiresAt === 'number' ? candidate.accessTokenExpiresAt : null,
|
||||
refreshToken: typeof candidate.refreshToken === 'string' ? candidate.refreshToken : null,
|
||||
user: candidate.user ?? null,
|
||||
clientToken: typeof candidate.clientToken === 'string' ? candidate.clientToken : null,
|
||||
clientTokenExpiresAt:
|
||||
typeof candidate.clientTokenExpiresAt === 'number' ? candidate.clientTokenExpiresAt : null,
|
||||
desktopClient: candidate.desktopClient ?? null,
|
||||
}
|
||||
}
|
||||
|
||||
function persistSession(next: DesktopSession | null): void {
|
||||
session.value = next
|
||||
|
||||
@@ -124,6 +164,7 @@ function createAuthenticatedClient() {
|
||||
persistSession({
|
||||
...session.value,
|
||||
accessToken: tokens.accessToken,
|
||||
accessTokenExpiresAt: tokens.expiresAt ?? session.value.accessTokenExpiresAt,
|
||||
refreshToken: tokens.refreshToken,
|
||||
})
|
||||
},
|
||||
@@ -137,6 +178,7 @@ function createAuthenticatedClient() {
|
||||
return {
|
||||
accessToken: next.access_token,
|
||||
refreshToken: next.refresh_token,
|
||||
expiresAt: next.expires_at,
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -170,12 +212,143 @@ function createSessionBoundAuthenticatedClient(source: DesktopSession) {
|
||||
return {
|
||||
accessToken: next.access_token,
|
||||
refreshToken: next.refresh_token,
|
||||
expiresAt: next.expires_at,
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function expiresAtMs(value: number | null | undefined): number | null {
|
||||
if (!value || !Number.isFinite(value)) {
|
||||
return null
|
||||
}
|
||||
return value < 10_000_000_000 ? value * 1000 : value
|
||||
}
|
||||
|
||||
function isRenewDue(value: number | null | undefined, renewWindowMs: number): boolean {
|
||||
const expiresAt = expiresAtMs(value)
|
||||
if (!expiresAt) {
|
||||
return true
|
||||
}
|
||||
return expiresAt - Date.now() <= renewWindowMs
|
||||
}
|
||||
|
||||
async function refreshLoginTokens(current: DesktopSession): Promise<DesktopSession> {
|
||||
if (!current.refreshToken) {
|
||||
throw new Error('missing_refresh_token')
|
||||
}
|
||||
|
||||
const next = await createPublicClient().post<RefreshResponse, { refresh_token: string }>(
|
||||
'/api/auth/refresh',
|
||||
{ refresh_token: current.refreshToken },
|
||||
)
|
||||
|
||||
return {
|
||||
...current,
|
||||
accessToken: next.access_token,
|
||||
accessTokenExpiresAt: next.expires_at,
|
||||
refreshToken: next.refresh_token,
|
||||
}
|
||||
}
|
||||
|
||||
async function rotateRuntimeClientToken(current: DesktopSession): Promise<DesktopSession> {
|
||||
if (!current.clientToken || !current.desktopClient) {
|
||||
throw new Error('missing_desktop_client_token')
|
||||
}
|
||||
|
||||
const rotated = await window.desktopBridge?.app?.rotateRuntimeClientToken?.()
|
||||
if (!rotated) {
|
||||
throw new Error('desktop_client_rotate_unavailable')
|
||||
}
|
||||
|
||||
return applyRotatedDesktopClient(current, rotated)
|
||||
}
|
||||
|
||||
function applyRotatedDesktopClient(
|
||||
current: DesktopSession,
|
||||
rotated: DesktopClientRotateResponse,
|
||||
): DesktopSession {
|
||||
return {
|
||||
...current,
|
||||
clientToken: rotated.client_token,
|
||||
clientTokenExpiresAt: rotated.expires_at,
|
||||
desktopClient: rotated.client,
|
||||
}
|
||||
}
|
||||
|
||||
async function forceLogoutAfterRenewFailure(err: unknown): Promise<void> {
|
||||
console.warn('[desktop-session] token renewal failed', err)
|
||||
error.value = '登录状态已过期,请重新登录'
|
||||
persistSession(null)
|
||||
try {
|
||||
await window.desktopBridge?.app?.releaseRuntimeSession?.(true)
|
||||
} catch (releaseError) {
|
||||
console.warn('[desktop-session] release after renewal failure failed', releaseError)
|
||||
}
|
||||
await transitionWindowForLogout()
|
||||
}
|
||||
|
||||
async function renewAuthenticatedSession(options: { force?: boolean } = {}): Promise<void> {
|
||||
const current = session.value
|
||||
if (current?.mode !== 'authenticated') {
|
||||
return
|
||||
}
|
||||
|
||||
if (renewPromise) {
|
||||
return renewPromise
|
||||
}
|
||||
|
||||
renewPromise = (async () => {
|
||||
let next = current
|
||||
let expectedSession = current
|
||||
const needsAccessRefresh =
|
||||
options.force || isRenewDue(next.accessTokenExpiresAt, accessTokenRenewWindowMs)
|
||||
const needsClientRotate =
|
||||
options.force || isRenewDue(next.clientTokenExpiresAt, clientTokenRenewWindowMs)
|
||||
|
||||
if (!needsAccessRefresh && !needsClientRotate) {
|
||||
return
|
||||
}
|
||||
|
||||
if (needsAccessRefresh) {
|
||||
next = await refreshLoginTokens(next)
|
||||
if (session.value !== expectedSession) {
|
||||
return
|
||||
}
|
||||
persistSession(next)
|
||||
expectedSession = next
|
||||
}
|
||||
|
||||
if (needsClientRotate) {
|
||||
next = await rotateRuntimeClientToken(next)
|
||||
if (session.value !== expectedSession) {
|
||||
return
|
||||
}
|
||||
persistSession(next)
|
||||
expectedSession = next
|
||||
}
|
||||
})()
|
||||
.catch(async (err) => {
|
||||
await forceLogoutAfterRenewFailure(err)
|
||||
})
|
||||
.finally(() => {
|
||||
renewPromise = null
|
||||
})
|
||||
|
||||
return renewPromise
|
||||
}
|
||||
|
||||
function ensureTokenRenewTimer(): void {
|
||||
if (typeof window === 'undefined' || tokenRenewTimer) {
|
||||
return
|
||||
}
|
||||
|
||||
tokenRenewTimer = setInterval(() => {
|
||||
void renewAuthenticatedSession()
|
||||
}, tokenRenewCheckMs)
|
||||
}
|
||||
|
||||
function resolveFallbackPlatform(): string {
|
||||
const currentNavigator = globalThis.navigator as Navigator & {
|
||||
userAgentData?: { platform?: string }
|
||||
@@ -431,6 +604,7 @@ async function performLogin(payload: LoginRequest): Promise<void> {
|
||||
persistSession({
|
||||
mode: 'authenticated',
|
||||
accessToken: authData.access_token,
|
||||
accessTokenExpiresAt: authData.expires_at,
|
||||
refreshToken: authData.refresh_token,
|
||||
user: authData.user,
|
||||
clientToken: null,
|
||||
@@ -443,6 +617,7 @@ async function performLogin(payload: LoginRequest): Promise<void> {
|
||||
persistSession({
|
||||
mode: 'authenticated',
|
||||
accessToken: authData.access_token,
|
||||
accessTokenExpiresAt: authData.expires_at,
|
||||
refreshToken: authData.refresh_token,
|
||||
user: authData.user,
|
||||
clientToken: registered.client_token,
|
||||
@@ -474,6 +649,7 @@ async function enterPreview(): Promise<void> {
|
||||
persistSession({
|
||||
mode: 'preview',
|
||||
accessToken: null,
|
||||
accessTokenExpiresAt: null,
|
||||
refreshToken: null,
|
||||
user: createPreviewUser(),
|
||||
clientToken: 'preview-client-token',
|
||||
@@ -541,6 +717,8 @@ if (typeof window !== 'undefined') {
|
||||
}
|
||||
})
|
||||
void syncStoredDesktopClientDeviceInfo()
|
||||
ensureTokenRenewTimer()
|
||||
void renewAuthenticatedSession()
|
||||
}
|
||||
|
||||
export function useDesktopSession() {
|
||||
@@ -553,6 +731,7 @@ export function useDesktopSession() {
|
||||
isPreviewMode: computed(() => session.value?.mode === 'preview'),
|
||||
setApiBaseURL: persistApiBaseURL,
|
||||
syncRuntimeSession: syncRuntimeSessionToMain,
|
||||
renewSession: renewAuthenticatedSession,
|
||||
login,
|
||||
logout,
|
||||
enterPreview,
|
||||
|
||||
+2
@@ -1,6 +1,7 @@
|
||||
import type {
|
||||
CreatePublishJobResponse,
|
||||
DesktopAccountInfo,
|
||||
DesktopClientRotateResponse,
|
||||
DesktopPublishTaskListResponse,
|
||||
DesktopRuntimeSessionSyncRequest,
|
||||
ListDesktopPublishTasksParams,
|
||||
@@ -55,6 +56,7 @@ declare global {
|
||||
getAppSettings(): Promise<DesktopAppSettings>
|
||||
setAppSetting(key: keyof DesktopAppSettings, value: boolean): Promise<DesktopAppSettings>
|
||||
syncRuntimeSession(session: DesktopRuntimeSessionSyncRequest | null): Promise<null>
|
||||
rotateRuntimeClientToken(): Promise<DesktopClientRotateResponse>
|
||||
releaseRuntimeSession(revoke?: boolean): Promise<null>
|
||||
setWindowMode(
|
||||
mode: 'login' | 'main',
|
||||
|
||||
Reference in New Issue
Block a user