diff --git a/apps/desktop-client/src/main/account-binder.ts b/apps/desktop-client/src/main/account-binder.ts index dd87b30..e3dfc97 100644 --- a/apps/desktop-client/src/main/account-binder.ts +++ b/apps/desktop-client/src/main/account-binder.ts @@ -74,6 +74,12 @@ export interface PublishAccountLocalInspection { profile: PublishAccountProfile | null; } +interface ActiveBindOperation { + platformId: string; + window: BrowserWindow; + promise: Promise; +} + type ToutiaoMediaInfoResponse = { data?: { user?: { @@ -199,6 +205,9 @@ type DongchediAccountInfoResponse = { }; }; +const activeBindOperations = new Map(); +const MAX_CONCURRENT_BIND_WINDOWS = 2; + const TOUTIAO_LOGIN_URL = "https://mp.toutiao.com/auth/page/login"; const TOUTIAO_CONSOLE_HOME_URL = "https://mp.toutiao.com/profile_v4/index"; const TOUTIAO_WORKBENCH_URL_PATTERN = /^https:\/\/mp\.toutiao\.com\/profile_v4\//i; @@ -1502,26 +1511,80 @@ function createBoundWindow( return window; } +function focusBindWindow(window: BrowserWindow): void { + if (window.isDestroyed()) { + return; + } + + if (window.isMinimized()) { + window.restore(); + } + if (!window.isVisible()) { + window.show(); + } + + window.focus(); +} + +function cleanupInactiveBindOperations(): void { + for (const [platformId, operation] of activeBindOperations.entries()) { + if (operation.window.isDestroyed()) { + activeBindOperations.delete(platformId); + } + } +} + +function clearActiveBindOperation(platformId: string, window: BrowserWindow): void { + const operation = activeBindOperations.get(platformId); + if (operation?.window === window) { + activeBindOperations.delete(platformId); + } +} + export async function bindPublishAccount(platformId: string): Promise { const definition = publishBindingDefinitions[platformId]; if (!definition) { throw new Error(`desktop_publish_platform_not_supported:${platformId}`); } - const handle = createPendingSessionHandle(platformId); + cleanupInactiveBindOperations(); - return await new Promise((resolve, reject) => { - const window = createBoundWindow(`绑定 ${definition.label}`, definition.loginUrl, handle.session); + const existingOperation = activeBindOperations.get(platformId); + if (existingOperation && !existingOperation.window.isDestroyed()) { + focusBindWindow(existingOperation.window); + return existingOperation.promise; + } + + if (activeBindOperations.size >= MAX_CONCURRENT_BIND_WINDOWS) { + throw new Error("desktop_account_bind_limit_reached"); + } + + const handle = createPendingSessionHandle(platformId); + let window: BrowserWindow; + + try { + window = createBoundWindow(`绑定 ${definition.label}`, definition.loginUrl, handle.session); + } catch (error) { + forgetSessionHandle(handle.accountId); + throw error instanceof Error ? error : new Error("desktop_account_bind_failed"); + } + + const bindPromise = new Promise((resolve, reject) => { let finished = false; let checking = false; let detectReady = false; + const finalizeOperation = () => { + clearInterval(intervalHandle); + clearActiveBindOperation(platformId, window); + }; + const settleSuccess = (account: DesktopAccountInfo) => { if (finished) { return; } finished = true; - clearInterval(intervalHandle); + finalizeOperation(); attachSessionHandle(account.id, handle); resolve(account); if (!window.isDestroyed()) { @@ -1539,7 +1602,7 @@ export async function bindPublishAccount(platformId: string): Promise { + clearActiveBindOperation(platformId, window); if (!finished) { clearInterval(intervalHandle); forgetSessionHandle(handle.accountId); @@ -1663,6 +1727,15 @@ export async function bindPublishAccount(platformId: string): Promise { diff --git a/apps/desktop-client/src/renderer/lib/client-errors.ts b/apps/desktop-client/src/renderer/lib/client-errors.ts index 4441e67..f748222 100644 --- a/apps/desktop-client/src/renderer/lib/client-errors.ts +++ b/apps/desktop-client/src/renderer/lib/client-errors.ts @@ -53,6 +53,14 @@ function presentClientError(kind: ClientActionKind, error: unknown): ClientError }; } + if (message === "desktop_account_bind_limit_reached") { + return { + tone: "warning", + title: "授权窗口已达上限", + content: "当前最多同时打开 2 个授权窗口。请先完成或关闭已有授权窗口,再继续新的绑定。", + }; + } + if (message === "desktop_account_upsert_failed") { return { tone: "error",