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:
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user