fix(offline): cold-boot offline from the persisted session + profile
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 1m9s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m39s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 1m9s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m39s
An installed PWA relaunched with the network off hung on the splash: C1's precache served the shell, but bootstrap() adopted the session and fetched the profile over the network, which hung. Persist the profile (on every online adopt + refresh, cleared on logout) and short-circuit bootstrap when the deliberate offline flag is sticky-on: with a cached session + profile, skip the network and land straight in the offline lobby. Without a cached profile (never online), drop the sticky flag and boot online — the first launch must be online. - session.ts: saveProfile/loadProfile/clearProfile (mirror saveSession). - offline.ts: shouldBootOffline decision (unit-tested). - app.svelte.ts: persist in adoptSession + refreshProfile, clear on logout, the offline boot short-circuit in bootstrap. Docs: ARCHITECTURE. Online cold-start unaffected (e2e 196); the offline boot is contour-verified (the mock e2e cannot enter sticky-offline).
This commit is contained in:
@@ -43,6 +43,9 @@ import { parseStartParam } from './deeplink';
|
||||
import {
|
||||
clearOnboarding,
|
||||
clearSession,
|
||||
clearProfile,
|
||||
loadProfile,
|
||||
saveProfile,
|
||||
loadOnboarding,
|
||||
loadPrefs,
|
||||
loadSession,
|
||||
@@ -53,6 +56,8 @@ import {
|
||||
} from './session';
|
||||
import type { CoachSeries } from './coachmark';
|
||||
import { connection, reportOffline, reportOnline, resetConnection } from './connection.svelte';
|
||||
import { offlineMode, setOfflineMode } from './offline.svelte';
|
||||
import { shouldBootOffline } from './offline';
|
||||
import { isConnectionCode } from './retry';
|
||||
import { clearGameCache, getCachedGame, setCachedGame } from './gamecache';
|
||||
import { advanceCached } from './gamedelta';
|
||||
@@ -460,6 +465,7 @@ export async function refreshProfile(): Promise<void> {
|
||||
if (!app.session) return;
|
||||
try {
|
||||
app.profile = await gateway.profileGet();
|
||||
void saveProfile(app.profile); // keep the offline-boot cache fresh
|
||||
} catch {
|
||||
// Best-effort; the banner just stays as it was until the next fetch.
|
||||
}
|
||||
@@ -517,6 +523,9 @@ async function adoptSession(s: Session): Promise<void> {
|
||||
await saveSession(s);
|
||||
try {
|
||||
app.profile = await gateway.profileGet();
|
||||
// Persist the profile so a later offline cold start can launch from it (its variant
|
||||
// preferences, dictionary versions and display name) without a network fetch.
|
||||
void saveProfile(app.profile);
|
||||
// The live interface language follows the device — the explicit local choice (saved in
|
||||
// prefs) or the system guess made at bootstrap — and is no longer overridden from the
|
||||
// account here: the Telegram bot a user signs in through must not dictate the UI, so a
|
||||
@@ -790,6 +799,24 @@ export async function bootstrap(): Promise<void> {
|
||||
// and skipped in the mock build; see lib/pwa.svelte.
|
||||
registerServiceWorker();
|
||||
|
||||
// Deliberate offline mode carried over from a prior session: with no network the session adoption
|
||||
// + profile fetch below would hang the splash, so skip them and launch straight from the cached
|
||||
// session + profile into the offline lobby. Without both (e.g. cleared storage, or an install that
|
||||
// was never online), drop the sticky flag and fall through to the normal online flow — the first
|
||||
// launch must be online.
|
||||
if (offlineMode.active) {
|
||||
const [offlineSession, offlineProfile] = await Promise.all([loadSession(), loadProfile()]);
|
||||
if (shouldBootOffline({ offlineActive: true, hasSession: !!offlineSession, hasProfile: !!offlineProfile })) {
|
||||
gateway.setToken(offlineSession!.token);
|
||||
app.session = offlineSession!;
|
||||
app.profile = offlineProfile!;
|
||||
if (router.route.name === 'login' || router.route.name === 'confirm') navigate('/');
|
||||
app.ready = true;
|
||||
return;
|
||||
}
|
||||
setOfflineMode(false);
|
||||
}
|
||||
|
||||
const saved = await loadSession();
|
||||
// A VK ID web-link callback (?code&device_id&state) rides the URL after the redirect back
|
||||
// from VK; capture and clear it here (it needs the restored session to link against).
|
||||
@@ -1023,6 +1050,7 @@ export async function logout(): Promise<void> {
|
||||
app.splashDone = false;
|
||||
gateway.setToken(null);
|
||||
await clearSession();
|
||||
await clearProfile();
|
||||
app.session = null;
|
||||
app.profile = null;
|
||||
navigate('/login');
|
||||
|
||||
Reference in New Issue
Block a user