Madogiwa Blog

主に技術系の学習メモに使っていきます。

ブラウザの離脱イベント時にリクエストを投げるときにはsendBeaconを使ったほうが良いという話

最近、下記のようなページの離脱時のイベントを補足してサーバーにリクエストを投げるような処理を実装したのですが、

import axios from axios;

window.addEventListener('beforeunload', () => {
  axios.post('/logs', { event: 'beforeunload' });
})

ページの離脱にリクエストが送信されたり、されなかったり、ブラウザによって挙動が違っておりということがあり、そもそもMDNのドキュメントを見ると推奨されていないようでした😢

ユーザーエージェントは通常 unload ハンドラーの中で生成された非同期 XMLHttpRequest を無視する https://developer.mozilla.org/ja/docs/Web/API/Navigator/sendBeacon

では何が推奨されているかというと、sendBeaconという機能が推奨されているようなので、戒めも込めて使い方をメモしておきます。

sendBeaconとは?

sendBeaconとは公式ドキュメントに下記のように記載されているとおり、少量のリクエストをサーバーに送信するための機能のようです。

navigator.sendBeacon() メソッドは、 HTTP でウェブサーバーに非同期に少量のデータを送るために使用することができます。 これはアナリティクスデータの送信に関する全ての問題を解決します。データは確実に、非同期に、そして次のページの読み込みに影響を与えずに送信されます。 https://developer.mozilla.org/ja/docs/Web/API/Navigator/sendBeacon

構文は下記のように第一引数にリクエスト先のURL、第2引数にパラメーターを取るような形になっています。

navigator.sendBeacon(url, data);

// 今回のケースだと下記のような実装になる
window.addEventListener('beforeunload', () => {
  let payload = new FormData();
  payload.append(event, 'beforeunload');
  navigator.sendBeacon('/logs', payload)
});

またsendBeaconは、POSTメソッドとして送信されるのでセキュリティ的にも安心そうですね。

Set the return value to true, return the sendBeacon() call, and continue to run the following steps in parallel: Let req be a new request, initialized as follows: method POST https://w3c.github.io/beacon/#sec-sendBeacon-method

sendBeaconをRailsで使う

sendBeaconRailsで使うとき先程も記載した通り、sendBeaconはPOSTでの送信となるためCSRFまわりのtoken等を付与してリクエストを送信しないとActionController::InvalidAuthenticityTokenのエラーが発生してしまいます。

そこで通常と同じようにリクエストヘッダーにCSRFのtokenを付与することを考えるのですがsendBeaconはリクエストヘッダーを変更できません。。。

The sendBeacon() method does not provide ability to customize the request method, provide custom request headers, or change other processing properties of the request and response. https://w3c.github.io/beacon/#sec-sendBeacon-method

ではどうするのかというと、RailsではcsrfのtokenをPOSTパラメーターに含めても機能するので、

リクエストはトークンをフォームのparamsまたはヘッダーとして渡すことがあるので、Railsではいずれかのトークンがセッションcookie内のトークンと一致することだけが求められます。 https://techracho.bpsinc.jp/hachi8833/2017_10_23/46891

その方針で下記のように実装してあげると良さそうです。

window.addEventListener("beforeunload", () => {
  let payload = new FormData();
  const param = document.querySelector("meta[name=csrf-param]").getAttribute("content");
  const token = document.querySelector("meta[name=csrf-token]").getAttribute("content");
  payload.append(param, token);
  payload.append(event, "beforeunload");
  navigator.sendBeacon("/logs", payload)
});

これでCSRFのtokenを付与してサーバーにリクエストを送信できました!

sendBeaconの注意点

sendBeaconの注意点ですが、IEには対応していないようです😅

developer.mozilla.org

しかし、polyfillはあるようなのでなんとかなりそうです(おそらくXMLHttpRequesttext/plainの形式で送信するような感じになる)

github.com

おわりに

今回は、ブラウザの離脱イベントを取得する際にはsendBeaconを使用したほうが良さそうということで、使い方とかをまとめました。

今回に限らずですが、JavaScriptまわりの処理を実装するときは、きちんとMDNのドキュメントを読むこと、各主要ブラウザできちんと動作確認して、ちゃんと動くことを確認しておくことが大事ですね・・・!😭

参考

navigator.sendBeacon and Rails