Detecting Apple Silicon mac in JavaScript
Asked Answered
C

4

27

Is there a way to detect Apple Silicon Mac in JavaScript?
Properties in navigator don't seem to be very helpful, for example, navigator.platform is set to MacIntel and the user agent is exactly the same.

The why part: I have two versions of my software available, for Intel and for Apple Silicon. Asking users "Is your Mac Apple Silicon or Intel?" is not great.

Calctufa answered 4/12, 2020 at 15:58 Comment(5)
I'm not a Mac guy, but can't you offer a "Universal Binary?" That way, it doesn't matter what CPU a user's Mac has.Terri
Interestingly, Chrome's download page does ask the user what chip they have (Apple or Intel). Though, I think Chrome's download may be a Universal Binary, so it may not matter. support.google.com/chrome/answer/95346 (look under "Mac" and then "Check your Mac configuration")Terri
Electron doesn't offer universal binaries, here's the motivation: electronjs.org/blog/apple-siliconCalctufa
Chrome downloads just the installer, it checks the architecture and then gets the actual thing depending on it. For simple apps such approach is a bit overkill.Calctufa
Detection via WebGL Renderer is working as a Chrome-Only feature currently as browsers are trying to avoid detection of platforms. I made a test page for it here: browserstrangeness.github.io/detect_mac.gpu.htm and a mirror here: browserstrangeness.bitbucket.io/detect_mac.gpu.htm (I am pretty sure Google didn't intend this as a method of platform detection using Chrome either.)Taler
H
20

I have a solution, but it feels pretty fragile.

Check the OS, since Apple Silicon only exists with 10_15 or higher:

navigator.userAgent.match(/OS X 10_([789]|1[01234])/)

Check the GPU using webgl:

var w = document.createElement("canvas").getContext("webgl");
var d = w.getExtension('WEBGL_debug_renderer_info');
var g = d && w.getParameter(d.UNMASKED_RENDERER_WEBGL) || "";
if (g.match(/Apple/) && !g.match(/Apple GPU/)) {
   ...definitely arm...
}

If you see Apple GPU, then it's Safari which hides the GPU to prevent fingerprinting. Dig into capabilities:

if (w.getSupportedExtensions().indexOf("WEBGL_compressed_texture_s3tc_srgb") == -1) {
  ...probably arm...
}

(I compared the capabilities of my MacBook Pro to a new M1 Air, and that one is missing on the Air. All others were identical.)

The approach I'm taking is to give the user a choice, but use this test to choose the default.

If anyone has another idea of something that might be peculiar to help narrow down if it's an M1, I'm happy to experiment...

Update: As Romain points out in the comments, Apple added support for the missing extension in Big Sur, so this isn't working now (in Safari; it still works in Chrome & Firefox).

Hohenzollern answered 22/12, 2020 at 16:32 Comment(2)
Nice! I agree about choosing the default, this can be a good test, at least for now, when Apple Silicon percentage is slowly increasing.Calctufa
I have the MacBookAir M1 with the latest Safari and this extension is not missing anymore.Sarette
T
14

This is possible via navigator.userAgentData now, although it isn't supported on every browser.

await navigator.userAgentData.getHighEntropyValues(['architecture'])

{
  architecture: "arm"
  brands: [{…}, {…}, {…}],
  mobile: false,
  platform: "macOS"
}

https://developer.mozilla.org/en-US/docs/Web/API/NavigatorUAData/getHighEntropyValues

Toler answered 19/1, 2023 at 19:1 Comment(1)
Since I only need to test in Chrome this is an excellent solution for that (supported back to v90 at the time of this writing).Mosesmosey
B
3

There's a library you can use that gives you information about the operating system that's in Node.js - os (const os = require('os')). It has a method returning the cpu cores - os.cpus(), you can just take the first element and check if it's the exact model you want or if it simply contains "Apple M1" as a string in its model - let isM1 = cpuCore[0].model.includes("Apple M1").

You might also check the os.arch() method as it:

Returns the operating system CPU architecture for which the Node.js binary was compiled.

I would however advice high levels of caution when using the arch method because I have had several occurrences in which an M1 Mac returned x64 from os.arch() instead of arm64 which is the expected result.

EDIT: Might be better to replace cpuCore[0].model.includes("Apple M1") with either a regular expression or cpuCore[0].model.includes("Apple") since the earliest versions of M1 macs return 'Apple processor' under model.

Beatnik answered 14/10, 2021 at 10:38 Comment(4)
os.arch() doesn't return the current computer architecture — it returns the architecture for which the application has been built.Ivy
@Ivy What does application even mean in this context? I have literally quoted the official documentation why would you use a different definition? In most cases you would expect an M1 processor to be using a M1 compiled version of Node, that's why I said that arm64 is the expected result. It's in fact so expected for Node.js to be compiled by your current OS that process.arch which is the exact same function implies they are the same in the example given in the docs - nodejs.org/api/process.html#process_process_archBeatnik
Sorry. I should clarify. I should have given context and not just blindly wrote the comment. The use case I am talking about is Electron. When having an Electron application and need to know the computer architecture but os.arch() gives the architecture of the app and this depends on what version of the app the user has downloaded.Ivy
The problem with os.arch is depicted here: github.com/nodejs/node/issues/41900#issuecomment-1113511254Casarez
M
1

I use UAParser.js V2 for this, which is free and open source. https://github.com/faisalman/ua-parser-js

In V2 they added a withClientHints chained function which returns a promise. Under the hood it uses the getHighEntropyValues method that user3717031 mentioned. It can be used like this:

UAParser().withClientHints().then(function(result) {
  var CPU = result.cpu.architecture; // returns "arm64" on macOS ARM
});
Massacre answered 23/12, 2023 at 22:0 Comment(2)
Since getHighEntropyValues doesn't work on Firefox or Safari, the result from ua-parser-js is not reliable. I created an issue asking them to use Joshua's solution instead github.com/faisalman/ua-parser-js/issues/732.Bledsoe
Nice find, thanks for commenting. I hope they make that changeMassacre

© 2022 - 2025 — McMap. All rights reserved.