src/lib/platform.ts
PURPOSE
Single source of truth for native/web platform detection. All code that needs to branch on iOS / Android / web imports this module instead of pulling Capacitor directly. Centralizing the dependency means the rest of the codebase has zero direct @capacitor/core imports outside this file.
OWNS
Platform— frozen object literal (as const) exporting four boolean predicates.Platform.isNative()— true on iOS or Android native shells.Platform.isIOS()— true when running inside iOS Capacitor shell.Platform.isAndroid()— true when running inside Android Capacitor shell.Platform.isWeb()— true in the browser (Vite dev / Vercel production / mobile web).
READS FROM
@capacitor/core—Capacitor.isNativePlatform()forisNative;Capacitor.getPlatform()returning'ios' | 'android' | 'web'for the three platform-specific predicates.
PUSHES TO
- Nothing. Pure read-only detection helpers. Callers consume return values directly; no events, no store mutations, no side effects.
DOES NOT
- Does not cache results — each call re-queries Capacitor. Capacitor’s platform identity is fixed for the app lifetime so the lookup is effectively constant, but no memoization layer exists here.
- Does not feature-detect (no UA sniffing, no
navigator.standalone, nowindow.matchMediachecks). All branching is delegated to Capacitor’s own detection. - Does not expose Capacitor’s
Capacitorobject — only the four predicates. Consumers cannot reach through to the underlying plugin API via this module. - Does not provide a
getPlatform()string accessor. Callers wanting the raw'ios' | 'android' | 'web'value must callCapacitor.getPlatform()directly (and thereby take a direct Capacitor dependency). - No tablet / phone distinction. No iOS version checks. No Android API-level checks.
Signals
- The four predicates are the only exported signals. Each is invoked as a function (
Platform.isNative()), not read as a property — calling code must respect the parentheses.
Entry points
Platform— named export, the frozen predicate bag. Idiomatic usage:import { Platform } from '@/lib/platform'; if (Platform.isNative()) { /* native-only path */ }.
Pattern notes
- Methods, not properties. Each predicate is a function returning boolean rather than a computed property. This defers the Capacitor query until call time, which matters during module initialization (some Capacitor builds populate platform state asynchronously on cold boot).
as constfreeze. Theas constassertion narrows the literal types and prevents reassignment of thePlatformobject’s properties — a deliberate guard against test code or plugins monkey-patching the predicates at runtime.- Chokepoint architecture. This file is the only sanctioned
@capacitor/coreimporter outside of Capacitor plugin wrappers. Any new platform-branching code should grow a predicate here rather than import Capacitor directly — that keeps the native dependency replaceable (e.g., swapping to a different shell or stubbing in tests). - Web is the default fallback. When running in Vite dev or on Vercel,
Capacitor.getPlatform()returns'web', soisWeb()is true and the three native predicates are all false. No additional environment detection needed.
EXTRACT-CANDIDATE
This file is itself the extraction target — it’s the canonical pattern for “wrap a third-party detection lib behind a small predicate API.” Future shared concepts that may warrant similar treatment:
- Capability detection (haptics availability, in-app-purchase availability, push-notification permission state). Currently scattered across plugin call sites; could collapse into a
Capabilitiesbag mirroring this module’s shape. - Viewport / input-mode detection (touch vs mouse, portrait vs landscape, safe-area insets). Today these live in ad-hoc hooks; a
Viewportpredicate module modeled onPlatformwould deduplicate the queries.