Files
OpenFrontIO/src/client/Platform.ts
T
Skigim f7598369ed refactor: consolidate platform detection across client components (#3325)
## Description:

This PR consolidates ad hoc platform/environment/viewport detection into
a single shared utility. It is scoped to this refactor only, and serves
as groundwork for the mobile-focused feature work planned for the v31
milestone.

### What changed
- Introduced a shared `Platform` utility centralising:
  - OS detection (with `userAgentData` + UA fallback)
  - Electron environment detection
- Viewport breakpoint helpers (`isMobileWidth`, `isTabletWidth`,
`isDesktopWidth`)
- Replaced duplicated inline checks across client files with the shared
API.
- Normalised Mac detection to derive from the consolidated OS logic
rather than a separate regex.

### Why
- Multiple client files each independently ran `navigator.userAgent`
regexes or copy-pasted `isElectron` logic — this unifies all of that.
- Puts a stable, tested abstraction in place before v31 mobile work
lands, so mobile feature branches have a consistent surface to build
against.

## Please complete the following:

- [x] I have added screenshots for all UI updates (N/A: refactor only,
no visible UI changes)
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file (N/A: no new user-facing strings)
- [x] I have added relevant tests to the test directory (N/A: refactor
only)
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

## Please put your Discord username so you can be contacted if a bug or
regression is found:

skigim
2026-03-02 10:12:48 -08:00

113 lines
2.8 KiB
TypeScript

export const Platform = (() => {
const isBrowser =
typeof window !== "undefined" && typeof navigator !== "undefined";
const normalizePlatform = (platform: string): string => {
const normalized = platform.toLowerCase();
if (normalized.includes("windows")) return "Windows";
if (
normalized.includes("iphone") ||
normalized.includes("ipad") ||
normalized.includes("ipod") ||
normalized.includes("ios")
) {
return "iOS";
}
if (
normalized.includes("mac") ||
normalized.includes("macintosh") ||
normalized.includes("macos")
) {
return "macOS";
}
if (normalized.includes("android")) return "Android";
if (normalized.includes("chrome os")) return "Linux";
if (normalized.includes("linux")) return "Linux";
return "Unknown";
};
// OS Extraction
const extractOS = (): string => {
if (!isBrowser) return "Unknown";
const uaData = (navigator as any).userAgentData;
if (uaData?.platform) {
return normalizePlatform(uaData.platform);
}
const ua = navigator.userAgent;
if (/windows nt/i.test(ua)) return "Windows";
if (/iphone|ipad|ipod/i.test(ua)) return "iOS";
if (
/mac os x/i.test(ua) &&
((navigator.maxTouchPoints ?? 0) > 1 || /ipad/i.test(ua))
) {
return "iOS";
}
if (/mac os x/i.test(ua)) return "macOS";
if (/android/i.test(ua)) return "Android";
if (/linux/i.test(ua)) return "Linux";
return "Unknown";
};
const currentOS = extractOS();
// Environment Extraction
const performElectronCheck = (): boolean => {
// Renderer process
if (
typeof window !== "undefined" &&
typeof (window as any).process === "object" &&
(window as any).process.type === "renderer"
) {
return true;
}
// Main process
if (
typeof process !== "undefined" &&
typeof process.versions === "object" &&
!!process.versions.electron
) {
return true;
}
// Detect the user agent when the `nodeIntegration` option is set to false
if (
isBrowser &&
typeof navigator.userAgent === "string" &&
navigator.userAgent.indexOf("Electron") >= 0
) {
return true;
}
return false;
};
const isMac = currentOS === "macOS";
return {
os: currentOS,
isMac,
isWindows: currentOS === "Windows",
isIOS: currentOS === "iOS",
isAndroid: currentOS === "Android",
isLinux: currentOS === "Linux",
isElectron: performElectronCheck(),
get isMobileWidth(): boolean {
return isBrowser ? window.innerWidth < 768 : false;
},
get isTabletWidth(): boolean {
return isBrowser
? window.innerWidth >= 768 && window.innerWidth < 1024
: false;
},
get isDesktopWidth(): boolean {
return isBrowser ? window.innerWidth >= 1024 : false;
},
};
})();