// 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; }