yigarashiのブログ

学んだことや考えていることを書きます

User Agent文字列を使ったブラウザ判定の事例 2022年版

やむを得ず、User Agent文字列を使って特定のブラウザ向けにJavaScriptの処理を分岐する必要が生まれてしまったので、調査・検討のログを記事にまとめます。

基本的にはバッドプラクティスである

ユーザーエージェント文字列を用いたブラウザーの判定 - HTTP | MDN

まずはMDNがドキュメントを公開しているので読みましょう。要点は以下です。

  • 基本的にUser Agent文字列に基づいて処理を出し分けるのはバッドプラクティス
  • 多くのケースではUser Agent文字列を使うよりも良い手段がある
    • 例えば特定の機能の実装状況に基づく分岐を行いたければそれを直接検出する
  • それでもやむを得ない場合、User Agent文字列からブラウザ名、レンダリングエンジン、バージョン、OS、端末といった情報を取得することができる
    • ただし各ブラウザのUser Agent文字列は嘘をついていることもあり安定した手法ではない

特にChromeではUser Agent文字列の削減を目指しており、User Agent Client Hintsへの移行を進めようとしているようです。

今回わたしが扱った事例では、これらのドキュメントを読んでなお、User Agent文字列を使ったブラウザの判定が(現状は)マシな手段であると考えられたので実装することにしました。

User Agent文字列の厄介な事情

まずはMDNの記事から嫌な文章を抜粋します。

例えば Chrome は、 ChromeSafari の両方の文字列を含みます。ですから Safari を判定するには、 Safari の文字列があって Chrome の文字列がないことを確認する必要がありますし、 Chromium は自分自身を Chrome と報告することがよくあり、 Seamonkey は自分自身を Firefox として報告することが時々あります。

どうしてこんなことになっているのか、わたしは詳しい事情を存じ上げませんが、User Agent文字列を使ったブラウザ判定が筋の悪い行いだという気持ちにはなってきます。特に今回はiOSSafariであることを判定する必要があったので、こうした事情を無視できません。

少し個別のUser Agent文字列を調査してみましょう。例えば、手元のmacOS版のChrome(バージョン102.0.5005.115)のUser Agentは以下のようになっています。確かにSafariという文字列が含まれています。

Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36

iOS版のChromeUser Agent in Chrome for iOSによれば以下のようです。注目ポイントとしては、ChromeではなくCriOSとなっていますね。

Mozilla/5.0 (iPhone; CPU iPhone OS 10_3 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) CriOS/56.0.2924.75 Mobile/14E5239e Safari/602.1

Firefoxについても調べてみましょう。FirefoxFirefox ユーザーエージェント文字列リファレンス - HTTP | MDNにてさまざまな環境のUser Agent文字列を一覧しています。iOS版を抜粋してみます。やはりSafariという文字列が含まれています。また、FirefoxではなくFxiOSとなっています。

Mozilla/5.0 (iPhone; CPU iPhone OS 8_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) FxiOS/1.0 Mobile/12F69 Safari/600.1.4

このあたりまで調べて、初心者が軽い気持ちで手を出して良い領域ではないと感じました。個別のUser Agentを観測してそれらを網羅するコードを書いたとしても、それに当てはまらないケースが次から次へとやってくる様子が目に浮かびます。

コミュニティに頼る

こうした厄介な問題についてはコミュニティの力を借りたいものです。"npm browser detection"などのキーワードでググっていくといくつかのライブラリを発見することができます。ダウンロード数やGitHubのスター数から以下の2つが特に有望に見えます。

bowserのほうがダウンロード数やスター数は多いですが、最終リリースが2年前(2022/6/26現在)で、issueやpull requestへの応答も滞っていそうなのが不安ポイントではあります。detect-browserのほうは最終リリースが7ヶ月前(2022/6/26現在)でアクティブ度合いはまだ高いかもしれませんが、開発状況は大差ないように見えます。実装やテストをちらっと眺めると、いずれのライブラリも先にChromeFirefoxにマッチするルールを適用して、それらをすり抜けた上でSafariのルールにマッチするならSafariであると判定しているようです。iOS版も考慮されており、わたしがちょっと調査して気になったようなポイントは網羅されているように見えました。

自分でやるよりはずっとマシに思われたので、実績を重視してbowserを利用することにしました。

まとめ

やむをえず行ったUser Agent文字列を使ったブラウザ判定の調査・検討のログをまとめました。User Agent文字列を使ったブラウザ判定は不安定な手法です。今回はOSSに頼るのが一番マシな解であると判断しそのように進めました。非常に嫌な気持ちにさせられる仕事だったので、プラットフォームやブラウザごとの差異がより減っていくこと、やむを得ない場合の標準的な手法が整備されることを願うばかりです。