From b4dfb0409b7267c9d9886da9756902b1aff3db8f Mon Sep 17 00:00:00 2001 From: shuaiplus <2327005759@qq.com> Date: Sat, 13 Jun 2026 17:05:30 +0800 Subject: [PATCH] fix: improve network status handling and probe logic --- webapp/src/components/NetworkStatusBadge.tsx | 7 +----- webapp/src/lib/api/auth.ts | 3 +++ webapp/src/lib/app-auth.ts | 26 ++++++-------------- webapp/src/lib/network-status.ts | 26 +++++++++++++++++--- webapp/src/lib/vault-worker.ts | 2 -- 5 files changed, 35 insertions(+), 29 deletions(-) diff --git a/webapp/src/components/NetworkStatusBadge.tsx b/webapp/src/components/NetworkStatusBadge.tsx index 4f22cb2..8b61c74 100644 --- a/webapp/src/components/NetworkStatusBadge.tsx +++ b/webapp/src/components/NetworkStatusBadge.tsx @@ -23,7 +23,6 @@ export default function NetworkStatusBadge() { const Icon = status === 'online' ? Wifi : WifiOff; useEffect(() => { - let cancelled = false; let timer = 0; const checkService = async () => { @@ -31,10 +30,7 @@ export default function NetworkStatusBadge() { setCurrentNetworkStatus('offline'); return; } - const reachable = await probeNodeWardenService(); - if (!cancelled) { - setCurrentNetworkStatus(reachable ? 'online' : 'offline'); - } + await probeNodeWardenService(); }; const scheduleNextCheck = () => { @@ -62,7 +58,6 @@ export default function NetworkStatusBadge() { document.addEventListener('visibilitychange', handleVisibilityChange); return () => { - cancelled = true; unsubscribe(); window.clearTimeout(timer); window.removeEventListener('online', handleOnline); diff --git a/webapp/src/lib/api/auth.ts b/webapp/src/lib/api/auth.ts index 1014f2b..98f9e86 100644 --- a/webapp/src/lib/api/auth.ts +++ b/webapp/src/lib/api/auth.ts @@ -9,6 +9,7 @@ import type { TokenSuccess, } from '../types'; import type { AccountPasskeyAssertion, AccountPasskeyPrfKeySet } from '../account-passkeys'; +import { recordNodeWardenReachable, recordNodeWardenUnreachable } from '../network-status'; import { parseJson, type AuthedFetch, type SessionSetter } from './shared'; const SESSION_KEY = 'nodewarden.web.session.v4'; @@ -474,6 +475,7 @@ export function createAuthedFetch(getSession: () => SessionState | null, setSess for (let attempt = 0; attempt < maxAttempts; attempt += 1) { try { const response = await fetch(input, { ...init, headers }); + recordNodeWardenReachable(); if (response.status !== 429 && (response.status < 500 || response.status >= 600)) { return response; } @@ -484,6 +486,7 @@ export function createAuthedFetch(getSession: () => SessionState | null, setSess } catch (error) { lastError = error; if (attempt === maxAttempts - 1) { + recordNodeWardenUnreachable(); throw error; } } diff --git a/webapp/src/lib/app-auth.ts b/webapp/src/lib/app-auth.ts index 4d1fe1b..7be9f47 100644 --- a/webapp/src/lib/app-auth.ts +++ b/webapp/src/lib/app-auth.ts @@ -279,20 +279,16 @@ export async function hydrateLockedSession( fallbackProfile: Profile | null = null ): Promise<{ session: SessionState | null; profile: Profile | null }> { const hasOfflineUnlock = hasOfflineUnlockRecord(session.email); - let serviceReachable = true; - if (hasOfflineUnlock) { - serviceReachable = await probeNodeWardenService(); - if (!serviceReachable) { - return { - session, - profile: fallbackProfile || loadOfflineProfileSnapshot(session.email), - }; - } + if (hasOfflineUnlock && browserReportsOffline()) { + return { + session, + profile: fallbackProfile || loadOfflineProfileSnapshot(session.email), + }; } const refreshedSession = await maybeRefreshSession(session); if (!refreshedSession?.accessToken) { - if (hasOfflineUnlock && !serviceReachable) { + if (hasOfflineUnlock && (browserReportsOffline() || !(await probeNodeWardenService()))) { return { session, profile: fallbackProfile || loadOfflineProfileSnapshot(session.email), @@ -571,14 +567,8 @@ export async function performUnlock( } }; - if (hasOfflineUnlock) { - if (browserReportsOffline()) { - return unlockOffline(); - } - const serviceReachable = await probeNodeWardenService(); - if (!serviceReachable) { - return unlockOffline(); - } + if (hasOfflineUnlock && browserReportsOffline()) { + return unlockOffline(); } let token: TokenSuccess | { TwoFactorProviders?: unknown; error_description?: string; error?: string }; diff --git a/webapp/src/lib/network-status.ts b/webapp/src/lib/network-status.ts index 7667532..f390ce1 100644 --- a/webapp/src/lib/network-status.ts +++ b/webapp/src/lib/network-status.ts @@ -1,12 +1,14 @@ export type NetworkStatus = 'online' | 'offline'; -const STATUS_PROBE_TIMEOUT_MS = 3500; +const STATUS_PROBE_TIMEOUT_MS = 8000; const STATUS_PROBE_CACHE_MS = 5000; +const PROBE_FAILURES_BEFORE_OFFLINE = 2; const listeners = new Set<(status: NetworkStatus) => void>(); let currentStatus: NetworkStatus = getInitialNetworkStatus(); let pendingProbe: Promise | null = null; let lastProbeAt = 0; let lastProbeResult = currentStatus === 'online'; +let consecutiveProbeFailures = 0; export function browserReportsOffline(): boolean { return typeof navigator !== 'undefined' && navigator.onLine === false; @@ -35,8 +37,23 @@ export function subscribeNetworkStatus(listener: (status: NetworkStatus) => void }; } +export function recordNodeWardenReachable(): void { + consecutiveProbeFailures = 0; + lastProbeResult = true; + setCurrentNetworkStatus('online'); +} + +export function recordNodeWardenUnreachable(): void { + lastProbeResult = false; + consecutiveProbeFailures += 1; + if (browserReportsOffline() || consecutiveProbeFailures >= PROBE_FAILURES_BEFORE_OFFLINE) { + setCurrentNetworkStatus('offline'); + } +} + export async function probeNodeWardenService(): Promise { if (browserReportsOffline()) { + consecutiveProbeFailures = PROBE_FAILURES_BEFORE_OFFLINE; setCurrentNetworkStatus('offline'); return false; } @@ -68,8 +85,11 @@ export async function probeNodeWardenService(): Promise { .catch(() => false) .then((result) => { lastProbeAt = Date.now(); - lastProbeResult = result; - setCurrentNetworkStatus(result ? 'online' : 'offline'); + if (result) { + recordNodeWardenReachable(); + } else { + recordNodeWardenUnreachable(); + } return result; }) .finally(() => { diff --git a/webapp/src/lib/vault-worker.ts b/webapp/src/lib/vault-worker.ts index 581c7e5..eba458e 100644 --- a/webapp/src/lib/vault-worker.ts +++ b/webapp/src/lib/vault-worker.ts @@ -1,5 +1,4 @@ import type { Send } from './types'; -import { getCurrentNetworkStatus } from './network-status'; import type { DecryptSendsArgs, DecryptVaultCoreArgs, DecryptVaultCoreResult } from './vault-decrypt'; type WorkerSuccess = { id: number; ok: true; result: T }; @@ -13,7 +12,6 @@ const pending = new Map void; reject: (error: function getWorker(): Worker | null { if (typeof Worker === 'undefined') return null; if (worker) return worker; - if (getCurrentNetworkStatus() === 'offline') return null; worker = new Worker(new URL('../workers/vault-decrypt.worker.ts', import.meta.url), { type: 'module' }); worker.addEventListener('message', (event: MessageEvent>) => { const message = event.data;