User-Agent Client Hintsについてまとめてみた

この記事は [レコチョク Advent Calendar 2024](https://qiita.com/advent-calendar/2024/recochoku) の13日目の記事となります。 

## はじめに
こんにちは、株式会社レコチョク新卒2年目エンジニアの笹野です。
サーバーサイドエンジニアとして、主にdヒッツというサービスに携わっています。

今年よく聴いたアーティストランキングTOP3は夢喰NEON、ΛrlequiΩ、なにわ男子でアイドル強めでした。

今回は、私が最近に業務で扱ったUser-Agent Client Hintsについてまとめた記事になります。

## User-Agent Client Hintsとは？
GoogleがChrome向けに導入したUser-Agentです。
もちろんClient Hintsは従来のUser-Agentと利用目的は同じです。違う点はまるっと文字列で表現していた情報を個別に扱ってやりとりするようにしたところです。
昨今ではChrome以外のブラウザでも採用されてきています。  

また、必要な情報だけを扱うようにしたClient Hintsには下記のような利点があります。
- 不必要な情報をやりとりしないのでプライバシー保護の観点で強化
- 情報を個別に扱うことで取得の際に文字列から複雑な正規表現で情報を抽出する必要がない

## Client HintsでUser-Agentを取得する方法
ということでサーバとブラウザに分けて取得する方法を紹介します。
1. サーバで利用する場合
リクエストヘッダーに`Accept-CH`を追加し、扱いたい項目を許可してヘッダーでやり取りします。

| 項目名 | 内容 | 例 |
| - | - | - |
| Sec-CH-UA | ブラウザの種類とメジャーバージョン | "Google Chrome"、"87" |
| Sec-CH-UA-Platform | 通常はOS | "macOS" |
| Sec-CH-UA-Platform-Version | プラットフォームまたはOSのバージョン | "14.5.0" |
| Sec-CH-UA-Arch | デバイスのアーキテクチャ | "ARM64" |
| Sec-CH-UA-Mobile | モバイルかどうか | true="?1"、false="?0" |
| Sec-CH-UA-Full-Version | ブラウザのマイナーを含めたバージョン | "Google Chrome",  "130.0.6723.70" |
| Sec-CH-UA-Bitness | アーキテクチャのビット数 | 64 |

`Sec-CH-UA`と`Sec_CH-UA-Mobile`、`Sec-CH-UA-Platform`については、デフォルトでクライアントからサーバへのリクエストで送信されます。  
デフォルトの項目は利用ユーザそれぞれで被りが多いので低エントロピー、反対にそれ以外の情報は高エントロピーと表現されることがあります。  
また、表に挙げた項目は一部で他にも取得できる情報があります。([参考](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers#client_hints))

2. ブラウザで利用する場合
JavaScriptでは`navigator.userAgentData`から取得できます。
従来は`navigator.userAgent`から取得していましたが、末尾に`Data`を付けるだけでClient Hintsを利用できます。

```
console.log(navigator.userAgentData);

// result
{
    "brands": [
      {
        "brand": "Google Chrome",
        "version": "131"
      },
      {
        "brand": "Chromium",
        "version": "131"
      },
      {
        "brand": "Not_A Brand",
        "version": "24"
      }
    ],
    "mobile": false,
    "platform": "Windows"
}
```
`brands`については、ユーザーエージェント・スニッフィング回避を目的としてダミーの値も含めているそうです。  
※ユーザーエージェント・スニッフィング = 特定のブラウザのみでウェブページを閲覧できるようにしたシステムのこと  
ですので、`brands`はこれら3つから情報を抽出する処理が必要になります。

高エントロピーは`getHighEntropyValue()`を通して取得します。
```
navigator.userAgentData.getHighEntropyValues([
  "fullVersionList",
  "platformVersion"
])
.then(userAgent => {console.log(userAgent)} );

// result
{
  "brands": [
    // 割愛
  ],
  "fullVersionList": [
    {
      "brand": "Chromium",
      "version": "130.0.6723.70"
    },
    {
      "brand": "Google Chrome",
      "version": "130.0.6723.70"
    },
    {
      "brand": "Not?A_Brand",
      "version": "99.0.0.0"
    }
  ],
  "mobile": false,
  "platform": "macOS",
  "platformVersion": "14.5.0"
}
```
上の例だと、`brands`では確認できなかったブラウザのマイナーバージョンやOSのバージョンが取得できるようになっています。

## おわりに
今回はUser-Agent Client Hintsについてざっとまとめてみました。従来のUser-Agentに比べClient Hintsは、過不足なく情報を取得でき、配列的に扱えて無駄のない実装ができる点が良いと思いました。一方で、現時点では対応していないブラウザがあるので完全にClient Hintsに移行することは難しく、現状は例えば従来のUser-Agentの処理を残してClient Hintsの分岐を追加するという形の実装になってしまいます。そのあたりは今後に期待したいところです。

--- 

明日の [レコチョク Advent Calendar 2024](https://qiita.com/advent-calendar/2024/recochoku) は14日目
Galileo AIとVercelでAndroidハンズオンをサクッと用意した話
です。お楽しみに！

## 参考文献
- https://developer.chrome.com/docs/privacy-security/user-agent-client-hints?hl=ja
- https://user-agent-client-hints.glitch.me/
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers#client_hints