Safari の Private Click Measurement を試す

# はじめに

WebKitベースのSafariに、「[Private Click Measurement](https://webkit.org/blog/11529/introducing-private-click-measurement-pcm/)」が搭載された。
Private Click Measurement (PCM)は、ドメイン横断でのコンバージョン計測において長らく利用されてきたサードパーティCookieに代わる計測手法である。
PCMを用いることで、ユーザーがサイトAからサイトBに遷移し、サイトBでコンバージョンした際に、サイトAに対して「サイトAからの遷移したユーザーがコンバージョンした」という事実を伝えることができる。
元々広告とそこから発生するコンバージョンについての追跡手法として提案されていたが、広告に限らないということで名称が変更され正式リリースされた。

# SafariにおけるCookieの制約

元々（2010年〜2011年ごろ）、Safariはデフォルトでサードパーティクッキーを拒否してた。
その後、2017年にIntelligent Tracking Preventionが登場し、徐々にアップデートを重ねながら様々なプライバシー保護機能と共にクッキーに対する制約も強化されてきた。
古くはOmniture SiteCatalyst（後のAdobe Analytics）がCNAME設定による独自ドメイン対応を提供しており、Safariでの正確なアクセス解析にはこの設定によりファーストパーティクッキーを用いることが望ましいとされていた。

## サードパーティクッキー

現在のSafariはサードパーティクッキーを完全に拒否する。
`Set-Cookie` HTTPヘッダーを指定していても、親フレームと異なるドメインのリソースであれば無視され値は持続しない。

## ファーストパーティクッキー

### クライアントサイド

クライアントサイド、即ちJavaScriptが `document.cookie` に対して値をセットする場合、その値が保持されるのは24時間または7日間になっている。
基本的には7日間ですが、広告等からの流入など一定の条件でトラッカー認定される場合に24時間に短縮される。

アクセス解析の観点からは、リードタイムが長い商材やサイト訪問が習慣化していない場合など、再訪のインターバルが24時間や7日以上の場合に計測精度に影響する。

### サーバーサイド

現時点でサーバーサイドなファーストパーティクッキーは期限の制約はない。
`Set-Cookie` ヘッダーで指定した値は、指定期間通り有効となる。

### CNAMEクローキング

[CNAME Cloaking and Bounce Tracking Defense](https://webkit.org/blog/11338/cname-cloaking-and-bounce-tracking-defense/)で告知されている通り、CNAMEによってファーストパーティ化しているサードパーティツールのクッキーも有効期限が7日に短縮される。
他のブラウザではより厳しい制約を課しているものもあり、CNAMEによる対策の持続可能性があまり見込めないのではないか。

# Private Click Measurement

さて、PCMは上記のようなクッキーに対する制約を強めているSafariが提案・実装している機能で、ChromeのConversion Measurement API または Attribution Reporting API と類似する機能である。

基本的な動作は、

1. 遷移元サイト（サイトA）のリンク（Aタグ）にいくつかの属性を追加することで、遷移先（サイトB）にユーザーが向かう瞬間に「クリックして遷移した」事実と、「コンバージョンを報告する先」をSafariが記録する。
2. 遷移先（サイトB）のコンバージョン時に、サイトAに対するコンバージョンピクセルが発火、その際にSafariが指定する所定のパスにリダイレクトさせることで、「コンバージョンを報告すること」をトリガーする。
3. 24時間〜48時間の間のランダムな期間が経過後、またはそれ以降でブラウザが動作中の早いタイミングで、保持された「コンバージョンを報告する先」に所定のJSONデータがPOSTメソッドで送信される。


## PCMに対応する

### エンドポイントを用意する

PCMに対応した計測エンドポイントは3つのリクエストに対応する必要がある。

|パス|目的|
|:----|:----|
|`/hogehoge/?trigger_data=00&trigger_priority=00`|所謂コンバージョンピクセル、特に書式の指定はなし。PCMに必要な「Trigger data」と「Optional priority」を含む必要がある。このURLへのリクエストを下のトリガーイベントURLに302リダイレクトする必要がある。|
|`/.well-known/private-click-measurement/trigger-attribution/00/00`|PCMのトリガーイベント用URL。上のピクセルからリダイレクトされてくる。このURLの書式は厳密に定義されており、末尾のスラッシュは無し。一つ目の00部分が「Trigger data」、2つめが「Optional priority」|
|`/.well-known/private-click-measurement/report-attribution/`|PCMのレポートを受け取るエンドポイント。書式は固定されている。|

[最新のIngestly EndpointはPCM用エンドポイント](https://github.com/ingestly/ingestly-endpoint/blob/2e099d34b9ae2890196ecfba6af1a3fa95eea0c2/ingestly.vcl#L16-L23)を組み込んでいるので何もしなくても利用可能。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107873/ec79179f-1027-6f42-57fa-6daf2c60bc30.png)


### ピクセルタグを用意する

上記の1つめのエンドポイントにリクエストを送るタグを用意する。
[IngestlyのPCM対応モジュールのサンプル](https://github.com/ingestly/ingestly-pcm-attr/blob/main/conversion-pixel.html)のように、imgタグを生成する方法がベーシック。

このピクセルは「遷移先サイト」のコンバージョン等、なんらかのイベント発生地点に設置する。

なお、ピクセルタグを通じて「Trigger data」と「Optional priority」を渡す必要があるが、この2つに渡す値はいくつかの制約がある。

#### Trigger data

コンバージョンの種類を現すもの。
例えば資料請求は00、購入が01、お問い合わせは02、のように採番して管理する。

`00` から `15` までの16通りの値のみ指定できる。
ゼロ埋めなので文字列型でなければならない。

#### Optional priority

1つのTriggerに対して複数のイベントを送る場合の優先度を指定する。
`00` から `63` までの64通りが指定できる。
このpriorityはトリガー毎に指定でき、例えばコンバージョンファネルの各段階を計測する時に活用できる。

例えばカート追加、チェックアウト、決済、確認、コンバージョン完了 のように5つのステップがある場合、
Trigger dataは`00`で統一しておき、カート追加のpriorityを`00` 、決済を`02`、完了を`04`と指定し、レポートJSONはpriorityが大きいものが送信される。つまり、決済で離脱されると02が、完了まで到達すれば04がレポートされる。


### リンクに属性を追加する

遷移元サイトのリンクに以下の属性を追加する。

```html
<a href="https://advertiser-foo.com/"
attributionSourceId="123"
attributeon="https://advertiser-foo.com"
attributionDestination="https://publisher-bar.com"
>https://sandbox.hsano.jp/</a>
```

|属性|例|目的|
|:----|:----|:----|
|`attributionSourceId`|`123`|遷移元ページ等の識別子。送信されてくるレポートJSONの `source_id` として値が戻される｜
|`attributeon`|`https://advertiser-foo.com`|コンバージョン等のイベントが発生したページで、レポートJSONの `attributed_on_site ` に反映される|
|`attributionDestination`|`https://publisher-bar.com`|PCMがレポートJSONを送信する先のドメイン名。|


### リクエストを記録する

PCMのレポートはPOSTメソッドで、リクエストボディにJSONを含む形で送信される。
このため、単純なリクエストログでは値が記録されないことがあるので注意が必要。

IngestlyのPCMモジュールでは、リクエストURLが `(req.url ~ "^/\.well-known/(attribution-reporting|private-click-measurement)/.*")` にマッチする場合にログ出力することにしている。
その上で、Fastlyのログフォーマットに `"report_json":"%{json.escape(urldecode(req.postbody))}V"` のように指定することで、POSTされたJSONを記録している。

[IngestlyのPCMモジュールは](https://github.com/ingestly/ingestly-pcm-attr)を用いると、BigQueryに簡単にログを残せる。


## PCMのレポート内容

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107873/340d6acc-f90a-5092-a307-95cfc56285ed.png)


Safariが送信したPCMレポートJSONは以下の形であった。
実際にはJSONは整形されておらず、空白や改行のない1行の状態であった。

```json
{
  "source_engagement_type":"click",
  "source_site":"publisher-bar.com",
  "source_id":123,
  "attributed_on_site":"advertiser-foo.com",
  "trigger_data":0,
  "version":1
}
```

メモ：priorityがJSONに乗らないので、どのステップの情報か分からない？


# 疑問と現状

## PCM関連リクエストにデバイス識別情報はあるか？

リクエストヘッダーにCookieは無い（サードパーティなので当然ではある）。
URLにパラメーター等を付与することもできない。

## PCM用エンドポイントを専用のホスト（サブドメイン）で動かせるか？

試した限り、サブドメインが含まれるとトリガーイベントのリダイレクトの時点でPCMが発火しない。（Safari デバッグモードで確認）
これはWebKitブログでも言及されており、あくまでTLD+1（Zone Apex）が`.well-known/` URLをホストする必要があるため、CNAMEはおろかサイト本体に同居する形でないと機能しない。

## User-Agentはどうなる？

通常のウェブアクセスで記録されたUA
`Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Safari/605.1.15`

PCMレポートのリクエスト時に記録されたUA
`com.apple.WebKit.Networking/16611.3.10.1.3 CFNetwork/1240.0.4 Darwin (unknown version)`

macOSかiOSかの判別ができる情報がなくなっている？


# まとめ

PCMは既に利用できるので対応したピクセルを作るのは簡単。
サードパーティクッキーが利用できない昨今、ドメイン横断でコンバージョンを追跡できる唯一の方法となる。（ChromeはConversion/Attribution Reporting待ち）

実装に際して難しそうなのは、ドメインを分けずにサイトの直下に `.well-known/`で始まるパスを用意しなければならない点かもしれない。