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.