feat(desktop): dedupe bind windows per platform and cap concurrency at 2

Track active bindPublishAccount calls in a per-platform map so a repeat
invocation reuses and focuses the existing window instead of spawning
a second one, and reject new platforms once 2 bind windows are already
open. Surface desktop_account_bind_limit_reached as a warning modal in
the renderer so users know to finish or close an existing authorization
before starting another.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-20 11:11:21 +08:00
parent 12f972882b
commit 4014eff427
2 changed files with 86 additions and 5 deletions
+78 -5
View File
@@ -74,6 +74,12 @@ export interface PublishAccountLocalInspection {
profile: PublishAccountProfile | null;
}
interface ActiveBindOperation {
platformId: string;
window: BrowserWindow;
promise: Promise<DesktopAccountInfo>;
}
type ToutiaoMediaInfoResponse = {
data?: {
user?: {
@@ -199,6 +205,9 @@ type DongchediAccountInfoResponse = {
};
};
const activeBindOperations = new Map<string, ActiveBindOperation>();
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<DesktopAccountInfo> {
const definition = publishBindingDefinitions[platformId];
if (!definition) {
throw new Error(`desktop_publish_platform_not_supported:${platformId}`);
}
const handle = createPendingSessionHandle(platformId);
cleanupInactiveBindOperations();
return await new Promise<DesktopAccountInfo>((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<DesktopAccountInfo>((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<DesktopAcc
return;
}
finished = true;
clearInterval(intervalHandle);
finalizeOperation();
forgetSessionHandle(handle.accountId);
if (!window.isDestroyed()) {
window.close();
@@ -1653,6 +1716,7 @@ export async function bindPublishAccount(platformId: string): Promise<DesktopAcc
}, 1800);
window.on("closed", () => {
clearActiveBindOperation(platformId, window);
if (!finished) {
clearInterval(intervalHandle);
forgetSessionHandle(handle.accountId);
@@ -1663,6 +1727,15 @@ export async function bindPublishAccount(platformId: string): Promise<DesktopAcc
syncDetectionState();
void detectAndBind();
});
activeBindOperations.set(platformId, {
platformId,
window,
promise: bindPromise,
});
focusBindWindow(window);
return await bindPromise;
}
export async function openPublishAccountConsole(account: PublishAccountIdentity): Promise<void> {
@@ -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",