Identify Googlebooks

Googlebooks are built on the Android technology stack and paired with desktop foundations from ChromeOS. Most web applications should run the same on Googlebooks as on any other desktop device and will see the same User Agent string as ChromeOS:

Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0; Safari/537.36

To identify Googlebooks more accurately, for example, for analytics, or to direct users to download your compatible Android app, you need to request two User-Agent Client Hints: the Platform and the Mobile client hints. Both of these are low entropy hints, allowing you to get them without additional requests or permissions, just like you do with the User-Agent string. You can incorporate them directly into your existing detection.

UA-CH Header Value for Googlebooks
Sec-CH-UA-Platform "Android"
Sec-CH-UA-Mobile ?0 (False)

To detect this in the browser, use the navigator.userAgentData API:

const isGooglebook = window?.navigator?.userAgentData?.platform === 'Android' && window?.navigator?.userAgentData?.mobile === false;

Because of User-Agent reduction, a Googlebook specific platform was not added to the Platform client hint or user agent string, so to ensure there was a distinction between Googlebooks and the previous version of ChromeOS, the client hint was changed to "Android", reflecting that Googlebooks are compatible with Android apps.

Server-side detection

Because these are both low entropy client hints, they'll be sent to your server on initial request from the client and you can pick them up in one of the following ways:

Node.js

const platform = req.headers['sec-ch-ua-platform'] || 'Not Sent';
const mobile = req.headers['sec-ch-ua-mobile'] || 'Not Sent';

const cleanPlatform = platform.replace(/"/g, '');
const isGooglebook = cleanPlatform === 'Android' && mobile === '?0';

Java

String platform = exchange.getRequestHeaders().getFirst("Sec-CH-UA-Platform");
 if (platform == null) platform = "Not Sent";

String mobile = exchange.getRequestHeaders().getFirst("Sec-CH-UA-Mobile");
if (mobile == null) mobile = "Not Sent";

String cleanPlatform = platform.replace("\"", "");
boolean isGooglebook = cleanPlatform.equals("Android") && mobile.equals("?0");

Ruby

mobile = req['Sec-CH-UA-Mobile'] || 'Not Sent'

clean_platform = platform.gsub('"', '')
is_googlebook = clean_platform == 'Android' && mobile == '?0'

Python

platform = self.headers.get('Sec-CH-UA-Platform', 'Not Sent')
mobile = self.headers.get('Sec-CH-UA-Mobile', 'Not Sent')

clean_platform = platform.replace('"', '')
is_googlebook = clean_platform == 'Android' and mobile == '?0'

PHP

$mobile = $_SERVER['HTTP_SEC_CH_UA_MOBILE'] ?? 'Not Sent';

$cleanPlatform = str_replace('"', '', $platform);
$isGooglebook = ($cleanPlatform === 'Android' && $mobile === '?0');

Go

platform := r.Header.Get("Sec-CH-UA-Platform")
if platform == "" {
  platform = "Not Sent"
}
mobile := r.Header.Get("Sec-CH-UA-Mobile")
if mobile == "" {
  mobile = "Not Sent"
}

cleanPlatform := strings.ReplaceAll(platform, "\"", "")
isGooglebook := cleanPlatform == "Android" && mobile == "?0"

Other useful client hints

While the Platform and Mobile client hints are enough to let you know your users are using a Googlebook, you may want more information about their device. The [Sec-CH-UA](https://developer.mozilla.org/docs/Web/HTTP/Reference/Headers/Sec-CH-UA) client hint returns a comma-separated list of the major browser version and other brands associated with the device, which is more straightforward to parse than doing so from the User-Agent string. There's also the Form Factors client hint (Sec-CH-UA-Form-Factors), a high entropy hint, that provides a comma-separated list of form factors for the given device (Desktop, Tablet, Mobile, Automotive, and so on). While this is often a single form factor, all applicable form factors could be included, so a detachable device may return "Desktop", "Tablet", for example. If it's important to know precisely what form factor your users are on, and not only that it's not a mobile Android device, you can use the Critical-CH header:

GET / HTTP/1.1
Host: example.com

HTTP/1.1 200 OK
Content-Type: text/html
Accept-CH: Sec-CH-UA-Form-Factors
Vary: Sec-CH-UA-Form-Factors
Critical-CH: Sec-CH-UA-Form-Factors

If the client would have included a critical hint after processing the Accept-CH header, it will retry the request. This lets you serve an optimized page instead of the initial response. Because this adds an extra round trip, use it only for meaningful page changes that require the critical information.