22

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.

Antelle
  • 633
  • 1
  • 7
  • 11
  • 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. – gen_Eric Dec 04 '20 at 16:15
  • 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. https://support.google.com/chrome/answer/95346 (look under "Mac" and then "Check your Mac configuration") – gen_Eric Dec 04 '20 at 16:18
  • 2
    Electron doesn't offer universal binaries, here's the motivation: https://www.electronjs.org/blog/apple-silicon – Antelle Dec 04 '20 at 16:28
  • 3
    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. – Antelle Dec 04 '20 at 16:29
  • 3
    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: https://browserstrangeness.github.io/detect_mac.gpu.htm and a mirror here: https://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.) – Jeff Clayton Nov 23 '21 at 14:39

3 Answers3

15

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).

Joshua Smith
  • 3,689
  • 4
  • 32
  • 45
  • Nice! I agree about choosing the default, this can be a good test, at least for now, when Apple Silicon percentage is slowly increasing. – Antelle Dec 22 '20 at 20:10
  • 4
    I have the MacBookAir M1 with the latest Safari and this extension is not missing anymore. – Romain Feb 05 '21 at 16:37
8

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

user3717031
  • 123
  • 2
  • 8
  • 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). – Shalanah Jan 27 '23 at 18:29
5

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.

InsertKnowledge
  • 1,012
  • 1
  • 11
  • 17
  • 1
    `os.arch()` doesn't return the current computer architecture — it returns the architecture for which the application has been built. – astoilkov Oct 15 '21 at 11:04
  • 1
    @astoilkov 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 - https://nodejs.org/api/process.html#process_process_arch – InsertKnowledge Oct 15 '21 at 13:23
  • 1
    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. – astoilkov Oct 18 '21 at 07:34
  • The problem with `os.arch` is depicted here: https://github.com/nodejs/node/issues/41900#issuecomment-1113511254 – loretoparisi Apr 29 '22 at 17:35