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:
@@ -1989,30 +1989,11 @@ export async function resolvePublishAccountProfile(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function finalizeDetectedAccountForBind(
|
async function finalizeDetectedAccountForBind(
|
||||||
definition: PublishPlatformBindingDefinition,
|
_definition: PublishPlatformBindingDefinition,
|
||||||
context: DetectContext,
|
_context: DetectContext,
|
||||||
detected: DetectedAccount,
|
detected: DetectedAccount,
|
||||||
): Promise<DetectedAccount> {
|
): Promise<DetectedAccount> {
|
||||||
if (detected.avatarUrl) {
|
return detected;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isToutiaoLoginURL(url: string): boolean {
|
function isToutiaoLoginURL(url: string): boolean {
|
||||||
@@ -2722,9 +2703,14 @@ export async function bindPublishAccount(platformId: string): Promise<DesktopAcc
|
|||||||
let finished = false;
|
let finished = false;
|
||||||
let checking = false;
|
let checking = false;
|
||||||
let detectReady = false;
|
let detectReady = false;
|
||||||
|
let detectDebounceHandle: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
const finalizeOperation = () => {
|
const finalizeOperation = () => {
|
||||||
clearInterval(intervalHandle);
|
clearInterval(intervalHandle);
|
||||||
|
if (detectDebounceHandle) {
|
||||||
|
clearTimeout(detectDebounceHandle);
|
||||||
|
detectDebounceHandle = null;
|
||||||
|
}
|
||||||
clearActiveBindOperation(platformId, window);
|
clearActiveBindOperation(platformId, window);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2772,9 +2758,28 @@ export async function bindPublishAccount(platformId: string): Promise<DesktopAcc
|
|||||||
detectReady = isWindowReadyForDetection(window) || fallbackReady;
|
detectReady = isWindowReadyForDetection(window) || fallbackReady;
|
||||||
};
|
};
|
||||||
|
|
||||||
window.webContents.on("did-finish-load", syncDetectionState);
|
const scheduleDetectAndBind = (delayMs = 120) => {
|
||||||
window.webContents.on("did-navigate", syncDetectionState);
|
if (finished || window.isDestroyed()) {
|
||||||
window.webContents.on("did-navigate-in-page", syncDetectionState);
|
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(
|
window.webContents.on(
|
||||||
"did-fail-load",
|
"did-fail-load",
|
||||||
(_event, errorCode, _errorDescription, _validatedURL, isMainFrame) => {
|
(_event, errorCode, _errorDescription, _validatedURL, isMainFrame) => {
|
||||||
@@ -2844,7 +2849,7 @@ export async function bindPublishAccount(platformId: string): Promise<DesktopAcc
|
|||||||
normalizedDetected,
|
normalizedDetected,
|
||||||
);
|
);
|
||||||
|
|
||||||
await flushSessionPersistence(handle.session);
|
void flushSessionPersistence(handle.session);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const account = await upsertDesktopAccount({
|
const account = await upsertDesktopAccount({
|
||||||
@@ -2884,7 +2889,7 @@ export async function bindPublishAccount(platformId: string): Promise<DesktopAcc
|
|||||||
|
|
||||||
const intervalHandle = setInterval(() => {
|
const intervalHandle = setInterval(() => {
|
||||||
void detectAndBind();
|
void detectAndBind();
|
||||||
}, 1800);
|
}, 650);
|
||||||
|
|
||||||
window.on("closed", () => {
|
window.on("closed", () => {
|
||||||
clearActiveBindOperation(platformId, window);
|
clearActiveBindOperation(platformId, window);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { getPlaywrightCDPPort, startHiddenPlaywrightReaper } from "./playwright-
|
|||||||
import { registerRendererDevtoolsProxyTarget } from "./renderer-devtools-proxy";
|
import { registerRendererDevtoolsProxyTarget } from "./renderer-devtools-proxy";
|
||||||
import { onRuntimeInvalidated } from "./runtime-events";
|
import { onRuntimeInvalidated } from "./runtime-events";
|
||||||
import {
|
import {
|
||||||
|
noteRuntimeAccountBound,
|
||||||
refreshRuntimeAccounts,
|
refreshRuntimeAccounts,
|
||||||
releaseRuntimeSession,
|
releaseRuntimeSession,
|
||||||
syncRuntimeSession,
|
syncRuntimeSession,
|
||||||
@@ -289,7 +290,12 @@ function registerBridgeHandlers(): void {
|
|||||||
ipcMain.handle("desktop:runtime-snapshot", () => createRuntimeSnapshot());
|
ipcMain.handle("desktop:runtime-snapshot", () => createRuntimeSnapshot());
|
||||||
safeHandle("desktop:bind-publish-account", async (_event, platformId: string) => {
|
safeHandle("desktop:bind-publish-account", async (_event, platformId: string) => {
|
||||||
const account = (await bindPublishAccount(platformId)) as DesktopAccountInfo;
|
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;
|
return account;
|
||||||
});
|
});
|
||||||
safeHandle("desktop:refresh-runtime-accounts", async () => {
|
safeHandle("desktop:refresh-runtime-accounts", async () => {
|
||||||
|
|||||||
@@ -1058,6 +1058,37 @@ export async function refreshRuntimeAccounts(): Promise<void> {
|
|||||||
await syncAccounts("manual");
|
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> {
|
export async function unbindRuntimeAccount(accountId: string, syncVersion: number): Promise<void> {
|
||||||
const matchedAccount = state.accounts.find((item) => item.id === accountId) ?? null;
|
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[] };
|
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 bindPendingPlatformId = ref<string | null>(null);
|
||||||
const openPendingAccountId = 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 account = await window.desktopBridge.app.bindPublishAccount(platformId);
|
||||||
const platformLabel = desktopMonitoringMediaCatalog.find((item) => item.id === platformId)?.label ?? platformId;
|
const platformLabel = desktopMonitoringMediaCatalog.find((item) => item.id === platformId)?.label ?? platformId;
|
||||||
showActionNotification("success", "绑定成功", `${account.display_name} 已绑定到 ${platformLabel}`);
|
showActionNotification("success", "绑定成功", `${account.display_name} 已绑定到 ${platformLabel}`);
|
||||||
await refreshAccounts();
|
await refresh();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showClientActionError("bind-account", error);
|
showClientActionError("bind-account", error);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
Reference in New Issue
Block a user