chore(frontend): introduce prettier + eslint and prune unused code
- Add Prettier 3 with prettier-plugin-organize-imports (sorts/removes unused imports) - Add ESLint 10 flat config with typescript-eslint + eslint-plugin-vue + eslint-config-prettier - Add root scripts: format, format:check, lint, lint:fix - Reformat 257 files across admin-web, ops-web, desktop-client, packages - Remove unused locals/exports flagged by --noUnusedLocals/--noUnusedParameters - Fix duplicate localTabLabel key, surrogate-pair regex u-flag, NBSP literal in regex - Skip server/ (Go) and apps/browser-extension/ (deprecated per ADR) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { computed, readonly, shallowRef } from "vue";
|
||||
import { computed, readonly, shallowRef } from 'vue'
|
||||
|
||||
import { ApiClientError, createApiClient } from "@geo/http-client";
|
||||
import { ApiClientError, createApiClient } from '@geo/http-client'
|
||||
import type {
|
||||
AuthTokens,
|
||||
DesktopClientInfo,
|
||||
@@ -11,93 +11,94 @@ import type {
|
||||
LoginResponse,
|
||||
RefreshResponse,
|
||||
UserInfo,
|
||||
} from "@geo/shared-types";
|
||||
} from '@geo/shared-types'
|
||||
|
||||
type SessionMode = "authenticated" | "preview";
|
||||
type SessionMode = 'authenticated' | 'preview'
|
||||
|
||||
interface DesktopSession {
|
||||
mode: SessionMode;
|
||||
accessToken: string | null;
|
||||
refreshToken: string | null;
|
||||
user: UserInfo | null;
|
||||
clientToken: string | null;
|
||||
clientTokenExpiresAt: number | null;
|
||||
desktopClient: DesktopClientInfo | null;
|
||||
mode: SessionMode
|
||||
accessToken: string | null
|
||||
refreshToken: string | null
|
||||
user: UserInfo | null
|
||||
clientToken: string | null
|
||||
clientTokenExpiresAt: number | null
|
||||
desktopClient: DesktopClientInfo | null
|
||||
}
|
||||
|
||||
interface ResolvedDeviceInfo {
|
||||
device_name: string;
|
||||
os: string;
|
||||
cpu_arch: string;
|
||||
device_name: string
|
||||
os: string
|
||||
cpu_arch: string
|
||||
}
|
||||
|
||||
const sessionStorageKey = "geo.desktop.session.v1";
|
||||
const apiBaseURLStorageKey = "geo.desktop.api-base-url";
|
||||
export const DEFAULT_API_BASE_URL = import.meta.env.VITE_DESKTOP_API_BASE_URL ?? "https://api.shengxintui.com";
|
||||
const sessionStorageKey = 'geo.desktop.session.v1'
|
||||
const apiBaseURLStorageKey = 'geo.desktop.api-base-url'
|
||||
export const DEFAULT_API_BASE_URL =
|
||||
import.meta.env.VITE_DESKTOP_API_BASE_URL ?? 'https://api.shengxintui.com'
|
||||
|
||||
const session = shallowRef<DesktopSession | null>(readStoredSession());
|
||||
const apiBaseURL = shallowRef(readStoredApiBaseURL());
|
||||
const pending = shallowRef(false);
|
||||
const error = shallowRef<string | null>(null);
|
||||
let loginPromise: Promise<void> | null = null;
|
||||
let logoutPromise: Promise<void> | null = null;
|
||||
const session = shallowRef<DesktopSession | null>(readStoredSession())
|
||||
const apiBaseURL = shallowRef(readStoredApiBaseURL())
|
||||
const pending = shallowRef(false)
|
||||
const error = shallowRef<string | null>(null)
|
||||
let loginPromise: Promise<void> | null = null
|
||||
let logoutPromise: Promise<void> | null = null
|
||||
|
||||
function readStoredSession(): DesktopSession | null {
|
||||
if (typeof window === "undefined") {
|
||||
return null;
|
||||
if (typeof window === 'undefined') {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
const raw = window.localStorage.getItem(sessionStorageKey);
|
||||
const raw = window.localStorage.getItem(sessionStorageKey)
|
||||
if (!raw) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
return JSON.parse(raw) as DesktopSession;
|
||||
return JSON.parse(raw) as DesktopSession
|
||||
} catch {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function persistSession(next: DesktopSession | null): void {
|
||||
session.value = next;
|
||||
session.value = next
|
||||
|
||||
if (typeof window === "undefined") {
|
||||
return;
|
||||
if (typeof window === 'undefined') {
|
||||
return
|
||||
}
|
||||
|
||||
if (!next) {
|
||||
window.localStorage.removeItem(sessionStorageKey);
|
||||
window.localStorage.removeItem(sessionStorageKey)
|
||||
} else {
|
||||
window.localStorage.setItem(sessionStorageKey, JSON.stringify(next));
|
||||
window.localStorage.setItem(sessionStorageKey, JSON.stringify(next))
|
||||
}
|
||||
|
||||
void syncRuntimeSessionToMain(next);
|
||||
void syncRuntimeSessionToMain(next)
|
||||
}
|
||||
|
||||
function readStoredApiBaseURL(): string {
|
||||
if (typeof window === "undefined") {
|
||||
return DEFAULT_API_BASE_URL;
|
||||
if (typeof window === 'undefined') {
|
||||
return DEFAULT_API_BASE_URL
|
||||
}
|
||||
|
||||
return window.localStorage.getItem(apiBaseURLStorageKey) ?? DEFAULT_API_BASE_URL;
|
||||
return window.localStorage.getItem(apiBaseURLStorageKey) ?? DEFAULT_API_BASE_URL
|
||||
}
|
||||
|
||||
function persistApiBaseURL(value: string): void {
|
||||
const trimmed = value.trim() || DEFAULT_API_BASE_URL;
|
||||
apiBaseURL.value = trimmed;
|
||||
const trimmed = value.trim() || DEFAULT_API_BASE_URL
|
||||
apiBaseURL.value = trimmed
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
window.localStorage.setItem(apiBaseURLStorageKey, trimmed);
|
||||
if (typeof window !== 'undefined') {
|
||||
window.localStorage.setItem(apiBaseURLStorageKey, trimmed)
|
||||
}
|
||||
|
||||
void syncRuntimeSessionToMain();
|
||||
void syncRuntimeSessionToMain()
|
||||
}
|
||||
|
||||
function createPublicClient() {
|
||||
return createApiClient({
|
||||
baseURL: apiBaseURL.value,
|
||||
timeoutMs: 15000,
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function createAuthenticatedClient() {
|
||||
@@ -109,34 +110,34 @@ function createAuthenticatedClient() {
|
||||
getRefreshToken: () => session.value?.refreshToken ?? null,
|
||||
setTokens: (tokens: AuthTokens) => {
|
||||
if (!session.value) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
persistSession({
|
||||
...session.value,
|
||||
accessToken: tokens.accessToken,
|
||||
refreshToken: tokens.refreshToken,
|
||||
});
|
||||
})
|
||||
},
|
||||
clearTokens: () => persistSession(null),
|
||||
async refresh(refreshToken: string): Promise<AuthTokens> {
|
||||
const next = await createPublicClient().post<RefreshResponse, { refresh_token: string }>(
|
||||
"/api/auth/refresh",
|
||||
'/api/auth/refresh',
|
||||
{ refresh_token: refreshToken },
|
||||
);
|
||||
)
|
||||
|
||||
return {
|
||||
accessToken: next.access_token,
|
||||
refreshToken: next.refresh_token,
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function createSessionBoundAuthenticatedClient(source: DesktopSession) {
|
||||
let accessToken = source.accessToken;
|
||||
let refreshToken = source.refreshToken;
|
||||
let accessToken = source.accessToken
|
||||
let refreshToken = source.refreshToken
|
||||
|
||||
return createApiClient({
|
||||
baseURL: apiBaseURL.value,
|
||||
@@ -145,79 +146,79 @@ function createSessionBoundAuthenticatedClient(source: DesktopSession) {
|
||||
getAccessToken: () => accessToken,
|
||||
getRefreshToken: () => refreshToken,
|
||||
setTokens: (tokens: AuthTokens) => {
|
||||
accessToken = tokens.accessToken;
|
||||
refreshToken = tokens.refreshToken;
|
||||
accessToken = tokens.accessToken
|
||||
refreshToken = tokens.refreshToken
|
||||
},
|
||||
clearTokens: () => {
|
||||
accessToken = null;
|
||||
refreshToken = null;
|
||||
accessToken = null
|
||||
refreshToken = null
|
||||
},
|
||||
async refresh(refreshTokenValue: string): Promise<AuthTokens> {
|
||||
const next = await createPublicClient().post<RefreshResponse, { refresh_token: string }>(
|
||||
"/api/auth/refresh",
|
||||
'/api/auth/refresh',
|
||||
{ refresh_token: refreshTokenValue },
|
||||
);
|
||||
)
|
||||
|
||||
return {
|
||||
accessToken: next.access_token,
|
||||
refreshToken: next.refresh_token,
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function resolveFallbackPlatform(): string {
|
||||
const currentNavigator = globalThis.navigator as Navigator & {
|
||||
userAgentData?: { platform?: string };
|
||||
};
|
||||
return currentNavigator.userAgentData?.platform || currentNavigator.platform || "unknown";
|
||||
userAgentData?: { platform?: string }
|
||||
}
|
||||
return currentNavigator.userAgentData?.platform || currentNavigator.platform || 'unknown'
|
||||
}
|
||||
|
||||
function resolveFallbackOS(platform: string): string {
|
||||
const normalized = platform.toLowerCase();
|
||||
if (normalized.includes("mac")) {
|
||||
return "darwin";
|
||||
const normalized = platform.toLowerCase()
|
||||
if (normalized.includes('mac')) {
|
||||
return 'darwin'
|
||||
}
|
||||
if (normalized.includes("win")) {
|
||||
return "windows";
|
||||
if (normalized.includes('win')) {
|
||||
return 'windows'
|
||||
}
|
||||
if (normalized.includes("linux")) {
|
||||
return "linux";
|
||||
if (normalized.includes('linux')) {
|
||||
return 'linux'
|
||||
}
|
||||
return normalized || "unknown";
|
||||
return normalized || 'unknown'
|
||||
}
|
||||
|
||||
function resolveFallbackDeviceName(platform: string): string {
|
||||
const normalized = platform.toLowerCase();
|
||||
if (normalized.includes("mac")) {
|
||||
return "Current Mac";
|
||||
const normalized = platform.toLowerCase()
|
||||
if (normalized.includes('mac')) {
|
||||
return 'Current Mac'
|
||||
}
|
||||
if (normalized.includes("win")) {
|
||||
return "Current Windows PC";
|
||||
if (normalized.includes('win')) {
|
||||
return 'Current Windows PC'
|
||||
}
|
||||
if (normalized.includes("linux")) {
|
||||
return "Current Linux Desktop";
|
||||
if (normalized.includes('linux')) {
|
||||
return 'Current Linux Desktop'
|
||||
}
|
||||
return "Current Desktop";
|
||||
return 'Current Desktop'
|
||||
}
|
||||
|
||||
async function resolveDeviceInfo(): Promise<ResolvedDeviceInfo> {
|
||||
try {
|
||||
const info = await window.desktopBridge?.app?.deviceInfo?.();
|
||||
const info = await window.desktopBridge?.app?.deviceInfo?.()
|
||||
if (info?.device_name && info.os && info.cpu_arch) {
|
||||
return info;
|
||||
return info
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn("[desktop-session] deviceInfo bridge failed", err);
|
||||
console.warn('[desktop-session] deviceInfo bridge failed', err)
|
||||
}
|
||||
|
||||
const platform = resolveFallbackPlatform();
|
||||
const platform = resolveFallbackPlatform()
|
||||
return {
|
||||
device_name: resolveFallbackDeviceName(platform),
|
||||
os: resolveFallbackOS(platform),
|
||||
cpu_arch: "unknown",
|
||||
};
|
||||
cpu_arch: 'unknown',
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveDesktopClientID(user: UserInfo): Promise<string> {
|
||||
@@ -225,53 +226,58 @@ async function resolveDesktopClientID(user: UserInfo): Promise<string> {
|
||||
tenant_id: user.tenant_id,
|
||||
workspace_id: user.primary_workspace_id,
|
||||
user_id: user.id,
|
||||
});
|
||||
})
|
||||
if (!clientID) {
|
||||
throw new Error("无法生成本机账号 client_id,请重启桌面客户端后重试");
|
||||
throw new Error('无法生成本机账号 client_id,请重启桌面客户端后重试')
|
||||
}
|
||||
return clientID;
|
||||
return clientID
|
||||
}
|
||||
|
||||
async function syncStoredDesktopClientDeviceInfo(): Promise<void> {
|
||||
const current = session.value;
|
||||
if (typeof window === "undefined" || current?.mode !== "authenticated" || !current.desktopClient || !current.user) {
|
||||
return;
|
||||
const current = session.value
|
||||
if (
|
||||
typeof window === 'undefined' ||
|
||||
current?.mode !== 'authenticated' ||
|
||||
!current.desktopClient ||
|
||||
!current.user
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const [device, clientID] = await Promise.all([
|
||||
resolveDeviceInfo(),
|
||||
resolveDesktopClientID(current.user),
|
||||
]);
|
||||
])
|
||||
if (session.value !== current) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
const desktopClient = current.desktopClient;
|
||||
const desktopClient = current.desktopClient
|
||||
if (desktopClient.id !== clientID) {
|
||||
try {
|
||||
const registered = await registerDesktopClient(current.user, device);
|
||||
const latest = session.value;
|
||||
if (latest?.mode !== "authenticated" || latest.user?.id !== current.user?.id) {
|
||||
return;
|
||||
const registered = await registerDesktopClient(current.user, device)
|
||||
const latest = session.value
|
||||
if (latest?.mode !== 'authenticated' || latest.user?.id !== current.user?.id) {
|
||||
return
|
||||
}
|
||||
persistSession({
|
||||
...latest,
|
||||
clientToken: registered.client_token,
|
||||
clientTokenExpiresAt: registered.expires_at,
|
||||
desktopClient: registered.client,
|
||||
});
|
||||
})
|
||||
} catch (err) {
|
||||
console.warn("[desktop-session] stable client migration failed", err);
|
||||
console.warn('[desktop-session] stable client migration failed', err)
|
||||
}
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
desktopClient.device_name === device.device_name
|
||||
&& desktopClient.os === device.os
|
||||
&& desktopClient.cpu_arch === device.cpu_arch
|
||||
desktopClient.device_name === device.device_name &&
|
||||
desktopClient.os === device.os &&
|
||||
desktopClient.cpu_arch === device.cpu_arch
|
||||
) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
persistSession({
|
||||
@@ -282,74 +288,76 @@ async function syncStoredDesktopClientDeviceInfo(): Promise<void> {
|
||||
os: device.os,
|
||||
cpu_arch: device.cpu_arch,
|
||||
},
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
async function registerPayload(
|
||||
user: UserInfo,
|
||||
device?: ResolvedDeviceInfo,
|
||||
): Promise<DesktopClientRegisterRequest> {
|
||||
const resolvedDevice = device ?? await resolveDeviceInfo();
|
||||
const resolvedDevice = device ?? (await resolveDeviceInfo())
|
||||
return {
|
||||
client_id: await resolveDesktopClientID(user),
|
||||
device_name: resolvedDevice.device_name,
|
||||
os: resolvedDevice.os,
|
||||
cpu_arch: resolvedDevice.cpu_arch,
|
||||
client_version: "0.1.0-dev",
|
||||
channel: "dev",
|
||||
};
|
||||
client_version: '0.1.0-dev',
|
||||
channel: 'dev',
|
||||
}
|
||||
}
|
||||
|
||||
async function registerDesktopClient(
|
||||
user: UserInfo,
|
||||
device?: ResolvedDeviceInfo,
|
||||
): Promise<DesktopClientRegisterResponse> {
|
||||
const resolvedDevice = device ?? await resolveDeviceInfo();
|
||||
const resolvedDevice = device ?? (await resolveDeviceInfo())
|
||||
return createAuthenticatedClient().post<
|
||||
DesktopClientRegisterResponse,
|
||||
DesktopClientRegisterRequest
|
||||
>("/api/desktop/clients/register", await registerPayload(user, resolvedDevice));
|
||||
>('/api/desktop/clients/register', await registerPayload(user, resolvedDevice))
|
||||
}
|
||||
|
||||
function createPreviewUser(): UserInfo {
|
||||
return {
|
||||
id: 0,
|
||||
email: "preview@geo-rankly.local",
|
||||
phone: "",
|
||||
name: "开发预览账号",
|
||||
email: 'preview@geo-rankly.local',
|
||||
phone: '',
|
||||
name: '开发预览账号',
|
||||
avatar_url: null,
|
||||
tenant_id: 0,
|
||||
primary_workspace_id: 0,
|
||||
tenant_role: "owner",
|
||||
permissions: ["desktop.preview"],
|
||||
tenant_role: 'owner',
|
||||
permissions: ['desktop.preview'],
|
||||
membership: null,
|
||||
kol_profile: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function createPreviewClient(): Promise<DesktopClientInfo> {
|
||||
const now = new Date().toISOString();
|
||||
const device = await resolveDeviceInfo();
|
||||
const now = new Date().toISOString()
|
||||
const device = await resolveDeviceInfo()
|
||||
return {
|
||||
id: "preview-client",
|
||||
id: 'preview-client',
|
||||
tenant_id: 0,
|
||||
workspace_id: 0,
|
||||
user_id: 0,
|
||||
device_name: `Preview ${device.device_name}`,
|
||||
os: device.os,
|
||||
cpu_arch: device.cpu_arch,
|
||||
client_version: "0.1.0-preview",
|
||||
channel: "dev",
|
||||
client_version: '0.1.0-preview',
|
||||
channel: 'dev',
|
||||
created_at: now,
|
||||
last_seen_at: now,
|
||||
last_rotated_at: now,
|
||||
revoked_at: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function buildRuntimeSessionPayload(next: DesktopSession | null): DesktopRuntimeSessionSyncRequest | null {
|
||||
function buildRuntimeSessionPayload(
|
||||
next: DesktopSession | null,
|
||||
): DesktopRuntimeSessionSyncRequest | null {
|
||||
if (!next) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -357,156 +365,161 @@ function buildRuntimeSessionPayload(next: DesktopSession | null): DesktopRuntime
|
||||
mode: next.mode,
|
||||
client_token: next.clientToken,
|
||||
desktop_client: next.desktopClient,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function syncRuntimeSessionToMain(next: DesktopSession | null = session.value): Promise<void> {
|
||||
if (typeof window === "undefined") {
|
||||
return;
|
||||
async function syncRuntimeSessionToMain(
|
||||
next: DesktopSession | null = session.value,
|
||||
): Promise<void> {
|
||||
if (typeof window === 'undefined') {
|
||||
return
|
||||
}
|
||||
|
||||
if (!window.desktopBridge?.app?.syncRuntimeSession) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
await window.desktopBridge.app.syncRuntimeSession(buildRuntimeSessionPayload(next));
|
||||
await window.desktopBridge.app.syncRuntimeSession(buildRuntimeSessionPayload(next))
|
||||
}
|
||||
|
||||
function formatLoginError(err: unknown): string {
|
||||
if (err instanceof ApiClientError) {
|
||||
const messages: Record<string, string> = {
|
||||
invalid_credentials: "账号或密码错误",
|
||||
login_rate_limited: "登录请求过于频繁",
|
||||
login_locked: "登录已被临时保护",
|
||||
login_in_progress: "登录请求正在处理中",
|
||||
login_guard_unavailable: "登录保护暂时不可用",
|
||||
};
|
||||
const message = messages[err.message] ?? err.message.replaceAll("_", " ");
|
||||
return err.detail ? `${message},${err.detail}` : message;
|
||||
invalid_credentials: '账号或密码错误',
|
||||
login_rate_limited: '登录请求过于频繁',
|
||||
login_locked: '登录已被临时保护',
|
||||
login_in_progress: '登录请求正在处理中',
|
||||
login_guard_unavailable: '登录保护暂时不可用',
|
||||
}
|
||||
const message = messages[err.message] ?? err.message.replaceAll('_', ' ')
|
||||
return err.detail ? `${message},${err.detail}` : message
|
||||
}
|
||||
return err instanceof Error ? err.message : "登录失败,请稍后重试";
|
||||
return err instanceof Error ? err.message : '登录失败,请稍后重试'
|
||||
}
|
||||
|
||||
async function performLogin(payload: LoginRequest): Promise<void> {
|
||||
pending.value = true;
|
||||
error.value = null;
|
||||
pending.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const authData = await createPublicClient().post<LoginResponse, LoginRequest>(
|
||||
"/api/auth/login",
|
||||
'/api/auth/login',
|
||||
payload,
|
||||
);
|
||||
)
|
||||
|
||||
persistSession({
|
||||
mode: "authenticated",
|
||||
mode: 'authenticated',
|
||||
accessToken: authData.access_token,
|
||||
refreshToken: authData.refresh_token,
|
||||
user: authData.user,
|
||||
clientToken: null,
|
||||
clientTokenExpiresAt: null,
|
||||
desktopClient: null,
|
||||
});
|
||||
})
|
||||
|
||||
const registered = await registerDesktopClient(authData.user);
|
||||
const registered = await registerDesktopClient(authData.user)
|
||||
|
||||
persistSession({
|
||||
mode: "authenticated",
|
||||
mode: 'authenticated',
|
||||
accessToken: authData.access_token,
|
||||
refreshToken: authData.refresh_token,
|
||||
user: authData.user,
|
||||
clientToken: registered.client_token,
|
||||
clientTokenExpiresAt: registered.expires_at,
|
||||
desktopClient: registered.client,
|
||||
});
|
||||
})
|
||||
} catch (err) {
|
||||
error.value = formatLoginError(err);
|
||||
persistSession(null);
|
||||
throw err;
|
||||
error.value = formatLoginError(err)
|
||||
persistSession(null)
|
||||
throw err
|
||||
} finally {
|
||||
pending.value = false;
|
||||
pending.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function login(payload: LoginRequest): Promise<void> {
|
||||
if (loginPromise) {
|
||||
return loginPromise;
|
||||
return loginPromise
|
||||
}
|
||||
|
||||
loginPromise = performLogin({ ...payload }).finally(() => {
|
||||
loginPromise = null;
|
||||
});
|
||||
return loginPromise;
|
||||
loginPromise = null
|
||||
})
|
||||
return loginPromise
|
||||
}
|
||||
|
||||
async function enterPreview(): Promise<void> {
|
||||
error.value = null;
|
||||
error.value = null
|
||||
persistSession({
|
||||
mode: "preview",
|
||||
mode: 'preview',
|
||||
accessToken: null,
|
||||
refreshToken: null,
|
||||
user: createPreviewUser(),
|
||||
clientToken: "preview-client-token",
|
||||
clientToken: 'preview-client-token',
|
||||
clientTokenExpiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000,
|
||||
desktopClient: await createPreviewClient(),
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
async function releaseSessionForLogout(current: DesktopSession | null): Promise<void> {
|
||||
if (current?.mode === "authenticated") {
|
||||
if (current?.mode === 'authenticated') {
|
||||
try {
|
||||
await window.desktopBridge?.app?.releaseRuntimeSession?.(true);
|
||||
await window.desktopBridge?.app?.releaseRuntimeSession?.(true)
|
||||
} catch (err) {
|
||||
console.warn("[desktop-session] releaseRuntimeSession failed", err);
|
||||
console.warn('[desktop-session] releaseRuntimeSession failed', err)
|
||||
}
|
||||
|
||||
try {
|
||||
await createSessionBoundAuthenticatedClient(current).post<null, Record<string, never>>("/api/auth/logout", {});
|
||||
await createSessionBoundAuthenticatedClient(current).post<null, Record<string, never>>(
|
||||
'/api/auth/logout',
|
||||
{},
|
||||
)
|
||||
} catch (err) {
|
||||
console.warn("[desktop-session] auth logout failed", err);
|
||||
console.warn('[desktop-session] auth logout failed', err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function transitionWindowForLogout(): Promise<void> {
|
||||
try {
|
||||
await window.desktopBridge?.app?.setWindowMode?.("login", { source: "logout", animate: true });
|
||||
await window.desktopBridge?.app?.setWindowMode?.('login', { source: 'logout', animate: true })
|
||||
} catch (err) {
|
||||
console.warn("[desktop-session] logout window transition failed", err);
|
||||
console.warn('[desktop-session] logout window transition failed', err)
|
||||
}
|
||||
}
|
||||
|
||||
async function performLogout(): Promise<void> {
|
||||
error.value = null;
|
||||
const current = session.value;
|
||||
const releasePromise = releaseSessionForLogout(current);
|
||||
error.value = null
|
||||
const current = session.value
|
||||
const releasePromise = releaseSessionForLogout(current)
|
||||
|
||||
persistSession(null);
|
||||
await transitionWindowForLogout();
|
||||
persistSession(null)
|
||||
await transitionWindowForLogout()
|
||||
|
||||
void releasePromise.catch((err) => {
|
||||
console.warn("[desktop-session] background logout release failed", err);
|
||||
});
|
||||
console.warn('[desktop-session] background logout release failed', err)
|
||||
})
|
||||
}
|
||||
|
||||
async function logout(): Promise<void> {
|
||||
if (logoutPromise) {
|
||||
return logoutPromise;
|
||||
return logoutPromise
|
||||
}
|
||||
|
||||
logoutPromise = performLogout().finally(() => {
|
||||
logoutPromise = null;
|
||||
});
|
||||
return logoutPromise;
|
||||
logoutPromise = null
|
||||
})
|
||||
return logoutPromise
|
||||
}
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
window.addEventListener("storage", (event) => {
|
||||
if (typeof window !== 'undefined') {
|
||||
window.addEventListener('storage', (event) => {
|
||||
if (event.key === apiBaseURLStorageKey) {
|
||||
apiBaseURL.value = event.newValue?.trim() || DEFAULT_API_BASE_URL;
|
||||
void syncRuntimeSessionToMain();
|
||||
apiBaseURL.value = event.newValue?.trim() || DEFAULT_API_BASE_URL
|
||||
void syncRuntimeSessionToMain()
|
||||
}
|
||||
});
|
||||
void syncStoredDesktopClientDeviceInfo();
|
||||
})
|
||||
void syncStoredDesktopClientDeviceInfo()
|
||||
}
|
||||
|
||||
export function useDesktopSession() {
|
||||
@@ -516,11 +529,11 @@ export function useDesktopSession() {
|
||||
pending: readonly(pending),
|
||||
error: readonly(error),
|
||||
isAuthenticated: computed(() => Boolean(session.value?.clientToken)),
|
||||
isPreviewMode: computed(() => session.value?.mode === "preview"),
|
||||
isPreviewMode: computed(() => session.value?.mode === 'preview'),
|
||||
setApiBaseURL: persistApiBaseURL,
|
||||
syncRuntimeSession: syncRuntimeSessionToMain,
|
||||
login,
|
||||
logout,
|
||||
enterPreview,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user