From a86a5e6839335fc2dfbcabda371ffa3d438d7b5c Mon Sep 17 00:00:00 2001 From: penguinway Date: Wed, 11 Mar 2026 15:46:05 +0800 Subject: [PATCH] fix(auto-update): revert checkNow to use performCheck (GitHub API) instead of electron-updater IPC checkNow was calling bridge.checkForUpdate() which invokes updater.checkForUpdates() via IPC. startAutoCheck() in the main process already calls checkForUpdates() on a 5s timer, and if that network request is still pending, the concurrent IPC call from checkNow hangs indefinitely, causing the UI to be stuck in "checking" state forever. Per the original design spec, checkNow should use performCheck() (GitHub API) directly. This is completely independent of electron-updater's internal state machine, so it never conflicts with the background startAutoCheck(). performCheck handles isCheckingRef, isChecking, hasUpdate, and latestRelease; checkNow only manages manualCheckStatus. --- application/state/useUpdateCheck.ts | 114 +++++----------------------- 1 file changed, 19 insertions(+), 95 deletions(-) diff --git a/application/state/useUpdateCheck.ts b/application/state/useUpdateCheck.ts index 4a2dbdee6..7401f0dda 100644 --- a/application/state/useUpdateCheck.ts +++ b/application/state/useUpdateCheck.ts @@ -235,117 +235,41 @@ export function useUpdateCheck(): UseUpdateCheckResult { }, []); const checkNow = useCallback(async (): Promise => { - // Prevent concurrent checks (same guard used by performCheck) + // Prevent concurrent checks (performCheck owns isCheckingRef) if (isCheckingRef.current) { debugLog('checkNow: already checking, skipping'); return null; } - isCheckingRef.current = true; + // Immediately reflect 'checking' in the UI setUpdateState((prev) => ({ ...prev, - isChecking: true, manualCheckStatus: 'checking', error: null, })); - try { - const bridge = netcattyBridge.get(); - const result = await bridge?.checkForUpdate?.(); - - // Bridge unavailable (no Electron context) - if (!result) { - setUpdateState((prev) => ({ - ...prev, - isChecking: false, - manualCheckStatus: 'error', - error: 'Update check unavailable', - })); - return null; - } - - // Platform does not support electron-updater (Linux deb/rpm/snap) → - // fall back to GitHub API for version awareness only (no auto-download) - if (result?.supported === false) { - // Read version from ref to avoid stale closure - const effectiveVersion = IS_UPDATE_DEMO_MODE ? '0.0.1' : currentVersionRef.current; - if (!effectiveVersion || effectiveVersion === '0.0.0') { - setUpdateState((prev) => ({ - ...prev, - isChecking: false, - manualCheckStatus: 'up-to-date', - })); - return null; - } - // Temporarily release the ref so performCheck can use its own guard - isCheckingRef.current = false; - const githubResult = await performCheck(effectiveVersion); - // performCheck already updates the state (hasUpdate, latestRelease, etc.) - // We only need to set manualCheckStatus from the result. - // isCheckingRef is now false (performCheck's finally cleared it), so finally - // block below is harmless. - setUpdateState((prev) => ({ - ...prev, - isChecking: false, - manualCheckStatus: githubResult?.hasUpdate ? 'available' : 'up-to-date', - })); - return null; - } - - // IPC returned an error - if (result?.error) { - setUpdateState((prev) => ({ - ...prev, - isChecking: false, - manualCheckStatus: 'error', - error: result.error ?? 'Update check failed', - })); - return null; - } - - if (result?.available && result.version) { - // autoDownload=true: electron-updater starts download immediately. - // The onUpdateAvailable IPC event will set autoDownloadStatus='downloading'. - // We set hasUpdate=true and populate latestRelease so UI can show version. - const now = Date.now(); - localStorageAdapter.writeNumber(STORAGE_KEY_UPDATE_LAST_CHECK, now); - setUpdateState((prev) => ({ - ...prev, - isChecking: false, - manualCheckStatus: 'available', - hasUpdate: true, - lastCheckedAt: now, - latestRelease: prev.latestRelease ?? { - version: result.version!, - tagName: `v${result.version}`, - name: `v${result.version}`, - body: typeof result.releaseNotes === 'string' ? result.releaseNotes : '', - htmlUrl: '', - publishedAt: result.releaseDate ?? new Date().toISOString(), - assets: [], - }, - })); - } else { - setUpdateState((prev) => ({ - ...prev, - isChecking: false, - manualCheckStatus: 'up-to-date', - })); - } - - return null; - } catch (err) { - const msg = err instanceof Error ? err.message : 'Unknown error'; + // Skip check for dev/invalid builds (demo mode overrides to '0.0.1' inside performCheck) + const effectiveVersion = IS_UPDATE_DEMO_MODE ? '0.0.1' : currentVersionRef.current; + if (!effectiveVersion || effectiveVersion === '0.0.0') { setUpdateState((prev) => ({ ...prev, - isChecking: false, - manualCheckStatus: 'error', - error: msg, + manualCheckStatus: 'up-to-date', })); return null; - } finally { - isCheckingRef.current = false; } + + // Delegate to performCheck (GitHub API) — completely independent of + // electron-updater's startAutoCheck() in the main process. + // performCheck sets isCheckingRef, isChecking, hasUpdate, latestRelease. + const result = await performCheck(effectiveVersion); + + setUpdateState((prev) => ({ + ...prev, + manualCheckStatus: + result === null ? 'error' : result.hasUpdate ? 'available' : 'up-to-date', + })); + + return null; }, [performCheck]); const dismissUpdate = useCallback(() => {