From 615caf594668ffdf523032c1e73120144cb90eac Mon Sep 17 00:00:00 2001 From: shuaiplus <2327005759@qq.com> Date: Tue, 9 Jun 2026 14:09:46 +0800 Subject: [PATCH] feat: improve offline PWA resilience --- webapp/src/App.tsx | 12 +- .../src/components/AppAuthenticatedShell.tsx | 2 + webapp/src/components/AuthViews.tsx | 7 +- webapp/src/components/NetworkStatusBadge.tsx | 86 +++++++++++++ webapp/src/components/StandalonePageFrame.tsx | 6 +- webapp/src/components/VaultPage.tsx | 30 +++++ webapp/src/components/vault/WebsiteIcon.tsx | 7 +- webapp/src/hooks/useVaultSendActions.ts | 120 ++++++++++++++++++ webapp/src/lib/api/auth.ts | 2 +- webapp/src/lib/app-auth.ts | 26 +++- webapp/src/lib/i18n/locales/en.ts | 1 + webapp/src/lib/i18n/locales/es.ts | 1 + webapp/src/lib/i18n/locales/ru.ts | 1 + webapp/src/lib/i18n/locales/zh-CN.ts | 1 + webapp/src/lib/i18n/locales/zh-TW.ts | 1 + webapp/src/lib/network-status.ts | 79 ++++++++++++ webapp/src/lib/offline-auth.ts | 3 +- webapp/src/lib/pwa.ts | 11 +- webapp/src/lib/vault-worker.ts | 2 + webapp/src/styles/auth.css | 14 +- webapp/src/styles/responsive.css | 12 ++ webapp/src/styles/shell.css | 25 ++++ webapp/vite.config.ts | 4 +- 23 files changed, 432 insertions(+), 21 deletions(-) create mode 100644 webapp/src/components/NetworkStatusBadge.tsx create mode 100644 webapp/src/lib/network-status.ts diff --git a/webapp/src/App.tsx b/webapp/src/App.tsx index 2af4674..0b9e078 100644 --- a/webapp/src/App.tsx +++ b/webapp/src/App.tsx @@ -434,6 +434,7 @@ export default function App() { (async () => { const boot = await bootstrapAppSession(initialBootstrap); if (!mounted) return; + if (sessionRef.current?.symEncKey || sessionRef.current?.symMacKey) return; setDefaultKdfIterations(boot.defaultKdfIterations); setRegistrationInviteRequired(boot.registrationInviteRequired); setJwtWarning(boot.jwtWarning); @@ -912,7 +913,7 @@ export default function App() { const vaultCoreQuery = useQuery({ queryKey: ['vault-core', vaultCacheKey], queryFn: () => loadVaultCoreSyncSnapshot(authedFetch, vaultCacheKey), - enabled: !IS_DEMO_MODE && phase === 'app' && !!session?.symEncKey && !!session?.symMacKey && !!vaultCacheKey, + enabled: !IS_DEMO_MODE && phase === 'app' && !!session?.accessToken && !!session?.symEncKey && !!session?.symMacKey && !!vaultCacheKey, staleTime: 30_000, }); const encryptedVaultCore = vaultCoreQuery.data || cachedVaultCore; @@ -923,7 +924,7 @@ export default function App() { const sendsQuery = useQuery({ queryKey: sendsQueryKey, queryFn: () => getSends(authedFetch), - enabled: !IS_DEMO_MODE && phase === 'app' && !!session?.symEncKey && !!session?.symMacKey && location === '/sends' && !encryptedSendsFromSync, + enabled: !IS_DEMO_MODE && phase === 'app' && !!session?.accessToken && !!session?.symEncKey && !!session?.symMacKey && location === '/sends' && !encryptedSendsFromSync, staleTime: 30_000, }); const encryptedSends = sendsQuery.data || encryptedSendsFromSync; @@ -952,13 +953,13 @@ export default function App() { const usersQuery = useQuery({ queryKey: ['admin-users', vaultCacheKey], queryFn: () => listAdminUsers(authedFetch), - enabled: !IS_DEMO_MODE && phase === 'app' && isAdmin && vaultInitialDecryptDone, + enabled: !IS_DEMO_MODE && phase === 'app' && !!session?.accessToken && isAdmin && vaultInitialDecryptDone, staleTime: 30_000, }); const invitesQuery = useQuery({ queryKey: ['admin-invites', vaultCacheKey], queryFn: () => listAdminInvites(authedFetch), - enabled: !IS_DEMO_MODE && phase === 'app' && isAdmin && vaultInitialDecryptDone, + enabled: !IS_DEMO_MODE && phase === 'app' && !!session?.accessToken && isAdmin && vaultInitialDecryptDone, staleTime: 30_000, }); const totpStatusQuery = useQuery({ @@ -1015,7 +1016,7 @@ export default function App() { useQuery({ queryKey: ['admin-backup-settings', vaultCacheKey], queryFn: () => backupActions.loadSettings(), - enabled: !IS_DEMO_MODE && phase === 'app' && isAdmin && vaultInitialDecryptDone, + enabled: !IS_DEMO_MODE && phase === 'app' && !!session?.accessToken && isAdmin && vaultInitialDecryptDone, staleTime: 30_000, }); @@ -1085,6 +1086,7 @@ export default function App() { setDecryptedFolders(result.folders); setDecryptedCiphers(result.ciphers); setVaultInitialDecryptDone(true); + if (!session.accessToken) return; const repairKey = `${session.accessToken}:${encryptedCiphers.map((cipher) => `${cipher.id}:${cipher.revisionDate || ''}`).join(',')}`; if (uriChecksumRepairAttemptRef.current !== repairKey) { uriChecksumRepairAttemptRef.current = repairKey; diff --git a/webapp/src/components/AppAuthenticatedShell.tsx b/webapp/src/components/AppAuthenticatedShell.tsx index f07aee3..0d4d077 100644 --- a/webapp/src/components/AppAuthenticatedShell.tsx +++ b/webapp/src/components/AppAuthenticatedShell.tsx @@ -3,6 +3,7 @@ import type { ComponentChildren } from 'preact'; import { useEffect, useRef, useState } from 'preact/hooks'; import { Link } from 'wouter'; import AppMainRoutes from '@/components/AppMainRoutes'; +import NetworkStatusBadge from '@/components/NetworkStatusBadge'; import ThemeSwitch from '@/components/ThemeSwitch'; import type { AppMainRoutesProps } from '@/components/AppMainRoutes'; import { t } from '@/lib/i18n'; @@ -237,6 +238,7 @@ export default function AppAuthenticatedShell(props: AppAuthenticatedShellProps) {props.currentPageTitle}