2f867b8e6c
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 15s
CI / ui (pull_request) Successful in 1m10s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m41s
An installed PWA (standalone web + confirmed email) warms the dictionaries for the player's enabled variants while online — on lobby entry and on a variant-preference change — using the per-variant version the profile now advertises, so a later switch to offline mode already has the data. - dict/preload.ts: pure preloadDicts (retry + linear backoff, honours the session dictionary miss-breaker); node-tested. - dict/preloadrun.ts: lazy browser orchestration (real getDawg), imported dynamically so the loader and move generator stay out of the main bundle. - offline.svelte.ts: kickDictPreload, gated by the pure, tested offlinePreloadEligible, plus the reactive first-lobby preload warning. - Header shows a poor-connection notice in the ad-banner slot on a first-lobby preload failure (offline.preloadWarning, en/ru). - App-entry bundle budget 112->113 for the irreducible main-side wiring (documented in bundle-size.mjs); the heavy parts remain lazy chunks. Docs: ARCHITECTURE offline-mode + dict-preload mechanism.
61 lines
2.7 KiB
TypeScript
61 lines
2.7 KiB
TypeScript
// Pure helpers for the deliberate offline MODE — its device-scoped persistence and the readiness
|
|
// decision — kept out of the reactive module (offline.svelte.ts) so they unit-test in the node env.
|
|
// The deliberate offline mode is distinct from connection.svelte.ts's transient "can we reach the
|
|
// gateway" signal: it is the player's own sticky choice, and it gates the network, tints the chrome
|
|
// and shows only local games.
|
|
|
|
import type { Variant } from './model';
|
|
|
|
const STORAGE_KEY = 'scrabble.offlineMode';
|
|
|
|
/** loadOfflinePref reads the persisted offline-mode flag (device-scoped); false when unset or when
|
|
* storage is unavailable, so a device that cannot persist simply starts online. */
|
|
export function loadOfflinePref(): boolean {
|
|
try {
|
|
return typeof localStorage !== 'undefined' && localStorage.getItem(STORAGE_KEY) === '1';
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/** saveOfflinePref persists the offline-mode flag (best-effort). */
|
|
export function saveOfflinePref(on: boolean): void {
|
|
try {
|
|
if (typeof localStorage !== 'undefined') localStorage.setItem(STORAGE_KEY, on ? '1' : '0');
|
|
} catch {
|
|
/* best-effort — a failed persist just reverts to online on the next launch */
|
|
}
|
|
}
|
|
|
|
/**
|
|
* offlineReady reports whether the device can play offline right now: at least one variant is
|
|
* enabled and every enabled variant's dictionary is already available on the device (hasDict). The
|
|
* offline toggle uses it to decide whether flipping to offline can succeed immediately.
|
|
*/
|
|
export function offlineReady(enabled: readonly Variant[], hasDict: (v: Variant) => boolean): boolean {
|
|
return enabled.length > 0 && enabled.every((v) => hasDict(v));
|
|
}
|
|
|
|
/** missingDicts lists the enabled variants whose dictionary is not yet available — the ones the
|
|
* toggle must fetch (or wait on) before offline mode can be entered. */
|
|
export function missingDicts(enabled: readonly Variant[], hasDict: (v: Variant) => boolean): Variant[] {
|
|
return enabled.filter((v) => !hasDict(v));
|
|
}
|
|
|
|
/**
|
|
* offlinePreloadEligible reports whether a background dictionary preload should run in this
|
|
* context: an installed standalone web PWA (not a Telegram/VK mini-app, not a plain browser tab)
|
|
* with a confirmed email, currently online. Elsewhere the preload is wasted bandwidth — the context
|
|
* has no offline mode to prepare for — so kickDictPreload skips it. Mirrors the Settings offline
|
|
* toggle's eligibility so the two never disagree.
|
|
*/
|
|
export function offlinePreloadEligible(opts: {
|
|
hasEmail: boolean;
|
|
standalone: boolean;
|
|
inTelegram: boolean;
|
|
inVK: boolean;
|
|
online: boolean;
|
|
}): boolean {
|
|
return opts.hasEmail && opts.standalone && !opts.inTelegram && !opts.inVK && opts.online;
|
|
}
|