mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-28 05:46:58 +02:00
Co-authored-by: Lambda <lambda@multica.ai> Co-authored-by: multica-agent <github@multica.ai>
98 lines
3.1 KiB
TypeScript
98 lines
3.1 KiB
TypeScript
/**
|
|
* Client-side OS + architecture detection for the /download page.
|
|
*
|
|
* Prefers the modern `navigator.userAgentData.getHighEntropyValues`
|
|
* API (Chromium), falling back to the UA string.
|
|
*
|
|
* Known limitation: Safari on macOS always reports `Intel Mac OS X`
|
|
* in the UA string even on Apple Silicon, and Safari does not
|
|
* implement userAgentData. This function therefore returns `arm64`
|
|
* as the best default for any Mac — UI surfaces a small hint that
|
|
* points Intel users to the architecture-specific download below.
|
|
*/
|
|
|
|
export type OSName = "mac" | "windows" | "linux" | "unknown";
|
|
export type Arch = "arm64" | "x64" | "unknown";
|
|
|
|
export interface DetectResult {
|
|
os: OSName;
|
|
arch: Arch;
|
|
/** True when arch came from userAgentData high-entropy values
|
|
* (i.e. we can trust the Intel vs arm distinction). False when
|
|
* we defaulted — UI should show the Intel Mac disclaimer. */
|
|
archConfident: boolean;
|
|
}
|
|
|
|
interface UADataRecord {
|
|
platform: string;
|
|
architecture: string;
|
|
}
|
|
|
|
interface UserAgentDataLike {
|
|
getHighEntropyValues?: (hints: string[]) => Promise<UADataRecord>;
|
|
}
|
|
|
|
function normalizePlatform(raw: string): OSName {
|
|
const p = raw.toLowerCase();
|
|
if (p.includes("mac") || p === "darwin") return "mac";
|
|
if (p.includes("win")) return "windows";
|
|
if (p.includes("linux")) return "linux";
|
|
return "unknown";
|
|
}
|
|
|
|
function normalizeArch(raw: string): Arch {
|
|
const a = raw.toLowerCase();
|
|
if (a === "arm" || a === "arm64" || a === "aarch64") return "arm64";
|
|
if (a === "x86" || a === "x86_64" || a === "amd64" || a === "x64") return "x64";
|
|
return "unknown";
|
|
}
|
|
|
|
export async function detectOS(): Promise<DetectResult> {
|
|
if (typeof navigator === "undefined") {
|
|
return { os: "unknown", arch: "unknown", archConfident: false };
|
|
}
|
|
|
|
// Modern Chromium: userAgentData with high-entropy values gives
|
|
// both the platform name and CPU architecture unambiguously.
|
|
const uaData = (navigator as unknown as { userAgentData?: UserAgentDataLike })
|
|
.userAgentData;
|
|
if (uaData?.getHighEntropyValues) {
|
|
try {
|
|
const data = await uaData.getHighEntropyValues([
|
|
"platform",
|
|
"architecture",
|
|
]);
|
|
const os = normalizePlatform(data.platform);
|
|
const arch = normalizeArch(data.architecture);
|
|
return { os, arch, archConfident: arch !== "unknown" };
|
|
} catch {
|
|
// Some browsers expose the API but reject high-entropy requests.
|
|
}
|
|
}
|
|
|
|
// Fallback: UA + navigator.platform. Safari on Mac lands here and
|
|
// cannot distinguish Apple Silicon from Intel.
|
|
const ua = navigator.userAgent;
|
|
const platform = navigator.platform || "";
|
|
|
|
const os: OSName = /Mac|iPhone|iPad|iPod/i.test(platform) || /Mac OS X/i.test(ua)
|
|
? "mac"
|
|
: /Win/i.test(platform) || /Windows/i.test(ua)
|
|
? "windows"
|
|
: /Linux/i.test(platform) || /Linux/i.test(ua)
|
|
? "linux"
|
|
: "unknown";
|
|
|
|
let arch: Arch = "unknown";
|
|
if (os === "mac") {
|
|
// Best default. Real Intel Mac users will see the architecture hint.
|
|
arch = "arm64";
|
|
} else if (/arm|aarch/i.test(ua)) {
|
|
arch = "arm64";
|
|
} else if (os !== "unknown") {
|
|
arch = "x64";
|
|
}
|
|
|
|
return { os, arch, archConfident: false };
|
|
}
|