diff --git a/src/lib/devicePresence.ts b/src/lib/devicePresence.ts index 4b6fdc8..cf551fb 100644 --- a/src/lib/devicePresence.ts +++ b/src/lib/devicePresence.ts @@ -61,19 +61,36 @@ export function formatClientIpDisplay(ip?: string | null): string { return ip.trim(); } +/** CSS px; tablets in portrait are often 600–800; phones are usually smaller. */ +const TABLET_MIN_SHORT_SIDE_PX = 480; + +function hasTouchCapability(): boolean { + if (typeof navigator === "undefined") return false; + const touchPoints = navigator.maxTouchPoints ?? 0; + return touchPoints > 0 || "ontouchstart" in window; +} + export function detectClientTypeFromUa(): string { if (typeof navigator === "undefined") return "unknown"; const ua = navigator.userAgent.toLowerCase(); const touchPoints = navigator.maxTouchPoints ?? 0; + const hasTouch = hasTouchCapability(); const shortSide = screenShortSidePx(); - // iPadOS 13+ "desktop" Safari: MacIntel + multi-touch, no "iPad" in UA string - const isIpad = - ua.includes("ipad") || - (navigator.platform === "MacIntel" && touchPoints > 1); + if (ua.includes("iphone") || ua.includes("ipod")) { + return "mobile"; + } + + if (ua.includes("ipad") || ua.includes("tablet")) { + return "tablet"; + } - if (isIpad || ua.includes("tablet")) { + // iPadOS 13+ "desktop" Safari: MacIntel + multi-touch, no "iPad" in UA string + if ( + (navigator.platform === "MacIntel" || ua.includes("macintosh")) && + touchPoints > 1 + ) { return "tablet"; } @@ -83,24 +100,30 @@ export function detectClientTypeFromUa(): string { const platform = uad.platform?.toLowerCase() ?? ""; if (platform.includes("android")) { if (!uad.mobile) return "tablet"; - if (touchPoints > 0 && shortSide >= 600) return "tablet"; + if (hasTouch && shortSide >= TABLET_MIN_SHORT_SIDE_PX) return "tablet"; return "mobile"; } + if (platform === "ios" && hasTouch) { + return "tablet"; + } } if (ua.includes("android")) { - // Many Android tablets still include "Mobile" in the UA string + // Warehouse slates almost always include "Mobile" in UA even when not phones if (!ua.includes("mobile")) return "tablet"; - if (touchPoints > 0 && shortSide >= 600) return "tablet"; - return "mobile"; + if (hasTouch && shortSide >= TABLET_MIN_SHORT_SIDE_PX) return "tablet"; + // Small Android phone + if (shortSide > 0 && shortSide < 400) return "mobile"; + return "tablet"; } - if (ua.includes("iphone") || ua.includes("ipod")) { + if (ua.includes("mobile")) { return "mobile"; } - if (ua.includes("mobile")) { - return "mobile"; + // Touch slate / Surface / kiosk with desktop-like UA + if (hasTouch && shortSide >= 400) { + return "tablet"; } return "desktop";