perf(bind): speed up account bind detection and runtime sync

Replace the 1.8s polling interval with a 650ms tick plus a debounced
navigation-driven detect, listen for page-title-updated to catch SPA
transitions, and flush the session asynchronously. The bind handler
now optimistically seeds the runtime account list via
noteRuntimeAccountBound and fires the full server refresh in the
background, so the UI reflects the new binding without waiting on a
round-trip.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-26 21:57:26 +08:00
parent 0ce3f51f62
commit 53bebb46be
4 changed files with 72 additions and 30 deletions
+32 -27
View File
@@ -1989,30 +1989,11 @@ export async function resolvePublishAccountProfile(
}
async function finalizeDetectedAccountForBind(
definition: PublishPlatformBindingDefinition,
context: DetectContext,
_definition: PublishPlatformBindingDefinition,
_context: DetectContext,
detected: DetectedAccount,
): Promise<DetectedAccount> {
if (detected.avatarUrl) {
return detected;
}
let resolved = detected;
for (let attempt = 0; attempt < 3; attempt += 1) {
await sleep(400 + attempt * 250);
const retried = await definition.detect(context).catch(() => null);
if (!retried) {
continue;
}
const sanitized = sanitizeDetectedAccount(retried);
resolved = sanitized;
if (sanitized.avatarUrl) {
break;
}
}
return resolved;
return detected;
}
function isToutiaoLoginURL(url: string): boolean {
@@ -2722,9 +2703,14 @@ export async function bindPublishAccount(platformId: string): Promise<DesktopAcc
let finished = false;
let checking = false;
let detectReady = false;
let detectDebounceHandle: ReturnType<typeof setTimeout> | null = null;
const finalizeOperation = () => {
clearInterval(intervalHandle);
if (detectDebounceHandle) {
clearTimeout(detectDebounceHandle);
detectDebounceHandle = null;
}
clearActiveBindOperation(platformId, window);
};
@@ -2772,9 +2758,28 @@ export async function bindPublishAccount(platformId: string): Promise<DesktopAcc
detectReady = isWindowReadyForDetection(window) || fallbackReady;
};
window.webContents.on("did-finish-load", syncDetectionState);
window.webContents.on("did-navigate", syncDetectionState);
window.webContents.on("did-navigate-in-page", syncDetectionState);
const scheduleDetectAndBind = (delayMs = 120) => {
if (finished || window.isDestroyed()) {
return;
}
if (detectDebounceHandle) {
clearTimeout(detectDebounceHandle);
}
detectDebounceHandle = setTimeout(() => {
detectDebounceHandle = null;
void detectAndBind();
}, delayMs);
};
const handleNavigationReady = () => {
syncDetectionState();
scheduleDetectAndBind();
};
window.webContents.on("did-finish-load", handleNavigationReady);
window.webContents.on("did-navigate", handleNavigationReady);
window.webContents.on("did-navigate-in-page", handleNavigationReady);
window.webContents.on("page-title-updated", () => scheduleDetectAndBind(250));
window.webContents.on(
"did-fail-load",
(_event, errorCode, _errorDescription, _validatedURL, isMainFrame) => {
@@ -2844,7 +2849,7 @@ export async function bindPublishAccount(platformId: string): Promise<DesktopAcc
normalizedDetected,
);
await flushSessionPersistence(handle.session);
void flushSessionPersistence(handle.session);
try {
const account = await upsertDesktopAccount({
@@ -2884,7 +2889,7 @@ export async function bindPublishAccount(platformId: string): Promise<DesktopAcc
const intervalHandle = setInterval(() => {
void detectAndBind();
}, 1800);
}, 650);
window.on("closed", () => {
clearActiveBindOperation(platformId, window);
+7 -1
View File
@@ -18,6 +18,7 @@ import { getPlaywrightCDPPort, startHiddenPlaywrightReaper } from "./playwright-
import { registerRendererDevtoolsProxyTarget } from "./renderer-devtools-proxy";
import { onRuntimeInvalidated } from "./runtime-events";
import {
noteRuntimeAccountBound,
refreshRuntimeAccounts,
releaseRuntimeSession,
syncRuntimeSession,
@@ -289,7 +290,12 @@ function registerBridgeHandlers(): void {
ipcMain.handle("desktop:runtime-snapshot", () => createRuntimeSnapshot());
safeHandle("desktop:bind-publish-account", async (_event, platformId: string) => {
const account = (await bindPublishAccount(platformId)) as DesktopAccountInfo;
await refreshRuntimeAccounts();
noteRuntimeAccountBound(account);
void refreshRuntimeAccounts().catch((error) => {
console.warn("[desktop-bind] background account refresh failed", {
message: error instanceof Error ? error.message : String(error),
});
});
return account;
});
safeHandle("desktop:refresh-runtime-accounts", async () => {
@@ -1058,6 +1058,37 @@ export async function refreshRuntimeAccounts(): Promise<void> {
await syncAccounts("manual");
}
export function noteRuntimeAccountBound(account: DesktopAccountInfo): void {
const normalizedAccount: DesktopAccountInfo = {
...account,
platform_uid: normalizeAccountPlatformUid(account.platform_uid) || account.platform_uid,
};
const existingIndex = state.accounts.findIndex((item) =>
item.id === normalizedAccount.id
|| (
item.platform === normalizedAccount.platform
&& sameAccountPlatformUid(item.platform_uid, normalizedAccount.platform_uid)
),
);
if (existingIndex >= 0) {
state.accounts = state.accounts.map((item, index) =>
index === existingIndex ? normalizedAccount : item,
);
} else {
state.accounts = [normalizedAccount, ...state.accounts];
}
state.accountProfiles.set(normalizedAccount.id, {
platformUid: normalizedAccount.platform_uid,
displayName: normalizedAccount.display_name,
avatarUrl: normalizedAccount.avatar_url,
});
state.lastAccountsSyncAt = Date.now();
syncTrackedAccounts(state.accounts.map((item) => accountIdentityFromDesktopAccount(item)));
refreshAccountNames();
}
export async function unbindRuntimeAccount(accountId: string, syncVersion: number): Promise<void> {
const matchedAccount = state.accounts.find((item) => item.id === accountId) ?? null;
@@ -17,7 +17,7 @@ import type { RuntimeAccount } from "../types";
type AccountRow = Readonly<Omit<RuntimeAccount, "tags">> & { tags: readonly string[] };
const { snapshot, refreshAccounts, loading } = useDesktopRuntime();
const { snapshot, refresh, refreshAccounts, loading } = useDesktopRuntime();
const bindPendingPlatformId = ref<string | null>(null);
const openPendingAccountId = ref<string | null>(null);
@@ -156,7 +156,7 @@ async function bindPlatform(platformId: string) {
const account = await window.desktopBridge.app.bindPublishAccount(platformId);
const platformLabel = desktopMonitoringMediaCatalog.find((item) => item.id === platformId)?.label ?? platformId;
showActionNotification("success", "绑定成功", `${account.display_name} 已绑定到 ${platformLabel}`);
await refreshAccounts();
await refresh();
} catch (error) {
showClientActionError("bind-account", error);
} finally {