// Pure PWA install helpers, kept out of the Svelte layer (and free of app imports) so the // platform branching is unit-testable in the node test env. The reactive runtime (the captured // install prompt, the Mini-App gate, the service-worker registration) lives in pwa.svelte.ts. /** InstallMode is how the install call-to-action resolves for the current platform. */ export type InstallMode = 'oneTap' | 'iosInstructions' | 'hidden'; /** * isStandalone reports whether the app is already running as an installed PWA — launched from * the home screen / desktop rather than a browser tab. Both the standard display-mode media * query and the iOS-only navigator.standalone flag are checked. Returns false with no window * (e.g. under the unit tests). */ export function isStandalone(): boolean { if (typeof window === 'undefined') return false; const displayMode = window.matchMedia?.('(display-mode: standalone)').matches ?? false; const iosStandalone = (window.navigator as Navigator & { standalone?: boolean }).standalone === true; return displayMode || iosStandalone; } /** * isIosSafari reports whether the app runs in Safari on iOS / iPadOS — the only browser there * that can add a site to the home screen, and one with no programmatic install (it never fires * beforeinstallprompt). In-app webviews (Telegram, VK) and the iOS Chrome/Firefox/Edge wrappers * are excluded, as is desktop Safari. Used to switch the CTA to manual Share -> Add-to-Home-Screen * instructions. */ export function isIosSafari(): boolean { if (typeof navigator === 'undefined') return false; const ua = navigator.userAgent; // iPadOS 13+ reports a Mac UA but exposes touch points — catch that as iOS too. const isIos = /iPhone|iPod|iPad/.test(ua) || (/Macintosh/.test(ua) && (navigator.maxTouchPoints ?? 0) > 1); if (!isIos) return false; // Safari only: the iOS Chrome (CriOS), Firefox (FxiOS), Edge (EdgiOS), Opera (OPiOS) and the // Google-app (GSA) wrappers all carry "Safari" but cannot add to the home screen from our CTA. return /Safari/.test(ua) && !/CriOS|FxiOS|EdgiOS|OPiOS|GSA/.test(ua); } /** * resolveInstallMode chooses how the install CTA should behave from the platform facts: * `deferredAvailable` — a beforeinstallprompt event was captured (Chromium one-tap); `standalone` * — already installed; `iosSafari` — manual instructions apply; `inMiniApp` — running inside a * Telegram/VK Mini App. The CTA is offered only on the plain web and never once installed. */ export function resolveInstallMode(facts: { deferredAvailable: boolean; standalone: boolean; iosSafari: boolean; inMiniApp: boolean; }): InstallMode { if (facts.inMiniApp || facts.standalone) return 'hidden'; if (facts.deferredAvailable) return 'oneTap'; if (facts.iosSafari) return 'iosInstructions'; return 'hidden'; }