* 仕様書

[REFS[
- [1] [CITE@en[RFC 8030 - Generic Event Delivery Using HTTP Push]], [TIME[2020-03-09 00:13:33 +09:00]] <https://tools.ietf.org/html/rfc8030>
- [2] [CITE@en-US[Push API]], [TIME[2020-02-04 16:21:34 +09:00]] <https://w3c.github.io/push-api/#security-and-privacy-considerations>
- [5] [CITE@en-US[Push API]], [TIME[2020-02-04 16:21:34 +09:00]] <https://w3c.github.io/push-api/#dom-pushmanager-subscribe>
- [73] [CITE@en-US[Push API]], [TIME[2020-02-04 16:21:34 +09:00]] <https://w3c.github.io/push-api/#dfn-push-subscription>
- [42] [CITE@en-US[Push API]], [TIME[2020-02-04 16:21:34 +09:00]] <https://w3c.github.io/push-api/#dom-pushmanager-getsubscription>
- [13] [CITE@en-US[Push API]], [TIME[2020-02-04 16:21:34 +09:00]] <https://w3c.github.io/push-api/#dom-pushsubscriptionoptions-applicationserverkey>
]REFS]

* 処理

[6] 
[CODE[PushManager]] の
[DFN[[CODE[subscribe]]]]
[[メソッド]]は、
次のようにしなければ[MUST[なりません]]。
[SRC[>>5]]

[FIG(steps)[
= [12] [VAR[オプション群]]を、
第1引数を省略可能な
[CODE[PushSubscriptionOptionsInit]]
[[辞書]]として解釈した結果に設定します。
= [7] [VAR[約束]]を、
[[新しい約束]]に設定します。
= [8] [VAR[約束]]を返します。
]FIG]

[9] [[非同期的]]に、次のようにしなければ[MUST[なりません]]。
[SRC[>>5]]

[FIG(steps)[
= [10] [[現在設定群オブジェクト]]が [[secure context]] で''ない''場合、
== [11] 
[VAR[約束]]を、
[CODE[SecurityError]] [CODE[DOMException]]
で[[拒絶]]し、
ここで停止します。
= [3] 
[VAR[オプション群]]について、
[F[[CODE[applicationServerKey]]]]
の正規化を実行します。
[[例外]]が[[投げ]]られた場合、
[VAR[約束]]を、
その[[例外]]で[[拒絶]]し、
ここで停止します。
[SEE[ [CODE[applicationServerKey]] ]]
@@
[4] 仕様書通りだと入力が直接編集されますが、実際そうなっているのかどうか?
= [21] 
[[プッシュサービス]]が[[公開鍵]]を必須とする場合
[SEE[ [[プッシュサービス]] ]]
であって、
[VAR[オプション群]]の
[F[[CODE[applicationServerKey]]]]
が
[CODE[null]]
の場合
[SRC[>>5, >>13]]、
== [22] 
[VAR[約束]]を、
[CODE[NotSupportedError]]
[CODE[DOMException]]
で[[拒絶]]し、
ここで停止します。
= [23] [VAR[登録]]を、
[[文脈オブジェクト]]の[F[サービスワーカー登録]]に設定します。
= [24] 
[VAR[登録]]の[F[活性ワーカー]]が [CODE[null]] の場合、
== [25] 
[VAR[約束]]を、
[CODE[InvalidStateError]]
[CODE[DOMException]]
で[[拒絶]]し、
ここで停止します。
= [26] [VAR[permission]] を、
[VAR[オプション群]]とこれが[[サービスワーカー]]からの呼び出しか否かについて、
[[permission]]
を尋ねた結果に設定します。
[SEE[ [[permission]] ]]
[[非同期的]]に待ちます。
= [27] 
[VAR[permission]]
が拒否の場合、
== [28] 
[VAR[約束]]を、
[CODE[NotAllowedError]]
[CODE[DOMException]]
で[[拒絶]]し、
ここで停止します。
= [40] 
当該[[サービスワーカー]]が[[プッシュ購読]]を持つ場合、
== [29] 
[VAR[購読]]を、
当該[[サービスワーカー]]の[[プッシュ購読]]に設定します。
[[非同期的]]に待ちます。
[NOTE[
[35] 
[[仕様書]]に明記されていませんが、
[[プッシュサービス]]への [[fetch]]
が想定されているようです。
]NOTE]
== [30] 
[VAR[購読]]がエラーの場合、
=== [31] 
[VAR[約束]]を、
[CODE[AbortError]]
[CODE[DOMException]]
で[[拒絶]]し、
ここで停止します。
=== [32] 
[VAR[購読]]の [F[[CODE[options]]]]
と[VAR[オプション群]]の[[属性]]を比較します。
[CODE[BufferSource]] は、等価性を比較します。
異なりがある場合、
==== [33] 
[VAR[約束]]を、
[CODE[InvalidStateError]]
[CODE[DOMException]]
で[[拒絶]]し、
ここで停止します。
== [34] 
[VAR[約束]]を、
[VAR[購読]]で[[解決]]します。
= [41] それ以外の場合、
== [36] [VAR[購読]]を、
[VAR[オプション群]]について[[プッシュ購読を作成]]した結果に設定します。
[[非同期的]]に待ちます。
== [37] 
[VAR[購読]]がエラーの場合、
=== [38] 
[VAR[約束]]を、
[CODE[AbortError]]
[CODE[DOMException]]
で[[拒絶]]し、
ここで停止します。
== [39] 
[VAR[約束]]を、
[VAR[購読]]で[[解決]]します。


]FIG]



-*-*-

[43] 
[CODE[PushManager]]
[[インターフェイス]]の
[DFN[[CODE[getSubscription]]]]
[[メソッド]]は、
次のようにしなければ[MUST[なりません]]。
[SRC[>>42]]

[FIG(steps)[
= [44] [VAR[約束]]を、
[[新しい約束]]に設定します。
= [45] [VAR[約束]]を返します。
]FIG]

[46] [[非同期的]]に、次のようにしなければ[MUST[なりません]]。
[SRC[>>42]]

[FIG(steps)[
= [47] 当該[[サービスワーカー]]が[[プッシュ購読]]を持つ場合、
== [50] 
[VAR[購読]]を、
当該[[サービスワーカー]]の[[プッシュ購読]]に設定します。
[[非同期的]]に待ちます。
[NOTE[
[51] 
[[仕様書]]に明記されていませんが、
[[プッシュサービス]]への [[fetch]]
が想定されているようです。
]NOTE]
== [52] 
[VAR[購読]]がエラーの場合、
=== [53] 
[VAR[約束]]を、
[CODE[AbortError]]
[CODE[DOMException]]
で[[拒絶]]し、
ここで停止します。
== [54] 
[VAR[約束]]を、
[VAR[購読]]で[[解決]]します。
= [48] それ以外の場合、
== [49] [VAR[約束]]を、
[CODE[null]]
で[[解決]]します。
]FIG]


-*-*-


[81] 
[[利用者エージェント]]は、
[DFN[[RUBYB[プッシュ購読の作成][create a push subscription]]]]を、
[CODE[PushSubscriptionOptions]]
[VAR[オプション群]]について、
次のようにしなければ[MUST[なりません]]。
[SRC[>>73]]

[FIG(steps)[
= [83] 
[VAR[プッシュ購読]]を、
新しい[[プッシュ購読]]に設定します。
[FIG(list members)[
: [F[[CODE[options]]]] :
[VAR[オプション群]]の[[複製][辞書の複製]]
]FIG]
= [80] 
[VAR[プッシュ購読]]について、
[CODE[aes128gcm]]
の[[プッシュ購読]]作成時の手順を実行します。
= [86] 
[VAR[プッシュ購読]]と[VAR[オプション群]]の [F[[CODE[applicationServerKey]]]]
について、
[[プッシュサービス資源]]を実行します。
[[非同期的]]に結果を待ちます。
[NOTE[
[111] 仕様書に明記がないですが、
[[fecth]]
なので失敗することがあります。
]NOTE]
= [87] [VAR[プッシュ購読]]を返します。

]FIG]

[110] [CODE[subscribe]] や[[更新][プッシュ購読の更新]]から呼び出されます。

-*-*-

[56] [[プッシュサービス資源]]の [[URL]] の決定については、
[[プッシュサービス]]の発見を参照。


[57] 
[[利用者エージェント]]は、いつでも新しい[[プッシュメッセージ購読]]を作成できなければ[MUST[なりません]]。
[SRC[>>1 8.2.]]

[58] 
[[利用者エージェント]]は、
新しい[[プッシュメッセージ購読]]を作成する際、
[[プッシュサービス資源]]に
[CODE[POST]] [[要求]]を送信します。
[SRC[>>1 4.]]


[16] 
[[利用者エージェント]]は、
以前の[[要求]]に対する[[応答]]で[[プッシュメッセージ購読集合]]が指定されていたら、
[[要求]]に[[リンク関係型]]
[CODE[urn:ietf:params:push:set]]
でこれを指定する[SHOULD[べきです]]。
[SRC[>>1 4.]]
[CODE[Link:]] [[HTTPヘッダー]]で記述できます。

[17] 
[[利用者エージェント]]は、
[[プッシュメッセージ購読]]の寿命の間、
[[プッシュメッセージ]]を集約して受信することができないなら、
[[プッシュメッセージ購読集合]]を省略して[MAY[構いません]]。
[[利用者エージェント]]が他の[[プッシュメッセージ]]受信者のかわりに[[プッシュメッセージ購読]]を監視するような場合に、
その必要があるかもしれません。
[SRC[>>1 4.1.]]

[71] 
[[利用者エージェント]]は、
[[VAPID]] で制限された[[プッシュメッセージ購読]]を作成したい場合、
[[公開鍵]]を指定します。
[CODE[Content-Type:]] は
[[MIME型]]
[DFN[[CODE[application/webpush-options+json]]]]、
[[要求本体]]は
[[RFC 7159]] [[JSONオブジェクト]]とします。
その
[DFN[[CODE[vapid]]]]
の値は、
[[公開鍵]]を
[[X9.62]] [[uncompressed form]]
で
[[RFC 7515]]
[[base64url]]
符号化したものとします。
[SRC[>>27]]

-*-*-

[70] 
[[プッシュサービス資源]]は、
[[プッシュメッセージ購読]]を作成します。
[[プッシュメッセージ購読資源]]と[[プッシュ資源]]が作成されます。
[[プッシュメッセージ購読集合]]を必要なら作成し
([[要求]]で指定されていればそれを選択し) て、
作成した[[プッシュメッセージ購読]]を追加します。

[19] 
[[プッシュサービス資源]]は、
[[要求]]で指定された[[プッシュメッセージ購読集合]]が非妥当なら、
[CODE[400]]
[[応答]]を返さなければ[MUST[なりません]]。
[SRC[>>1 4.1.]]
異なる[[利用者エージェント]]用の[[プッシュメッセージ購読集合]]が指定されるのは不適当でしょうし、
[[プッシュメッセージ購読集合]]でないものが指定された場合もそうでしょう。

[20] 
[[プッシュサービス資源]]は、
[[プッシュメッセージ購読集合]]の指定のない[[要求]]を、
[CODE[429]]
[[応答]]で拒絶して[MAY[構いません]]。
[SRC[>>1 4.1.]]
同じ[[利用者エージェント]]がいくつも[[プッシュメッセージ購読]]を作成し、
[[プッシュメッセージ購読集合]]にまとめられないのは不適当で不審な挙動と考えられます。

[69] 
同じ[[利用者エージェント]]であるかどうかの判断方法は、
[[実装]]依存です。
[SRC[>>1 4.1.]]
現在の
[[Webブラウザー]]の配布形態は、
特別な利用者登録なしに自由に実行できるものとなっていますから、
事前交換情報に基づく[[認証]]による確実な識別ができません。
[[IPアドレス]]その他から推測することになりますが、
どの方法であれ確実とはいえません。
おそらく、
[[Webブラウザー]]依存の方法で[[利用者エージェント]]を識別する何らかの情報を
[[Webブラウザー]]側で保持しておき、
これを毎回[[要求]]に追加して同一性判定に供することになるのでしょう。
[[プッシュサービス]]は任意の方法で[[認証]]できます [SRC[>>1 8.3.]]。

[66] 
[CODE[application/webpush-options+json]]
''以外''の[[要求本体]]は、
無視しなければ[MUST[なりません]]。
[SRC[>>27]]

[67] 
[CODE[application/webpush-options+json]]
[[要求本体]]のうち、
理解できない
[[JSONオブジェクト]]のメンバーは無視しなければ[MUST[なりません]]。
[SRC[>>27]]

[68] 
[[要求本体]]が[[JSONオブジェクト]]でないときどうするべきか不明です。

[64] 
[[プッシュサービス]]は、
[[公開鍵]]を必須にできます。
[SEE[ [[subscribe]] ]]

-*-*-

[14] 
作成に成功したら、[[応答]]を返します。

- [15] [CODE[201]]
[[応答]]を返します
[SRC[>>1 4.]]。
-
[59] 
[CODE(HTTP)@en[Location:]]
[[ヘッダー]]に、
作成された[[プッシュメッセージ購読資源]]の [[URL]]
を指定しなければ[MUST[なりません]]。
[SRC[>>1 4.]]
-
[61] 
[[プッシュメッセージ購読]]に対応する[[プッシュ資源]]の
[[URL]]
を[[リンク関係型]]
[CODE[urn:ietf:params:push]]
で記述しなければ[MUST[なりません]]。
[SRC[>>1 4.]]
[CODE[Link:]] [[ヘッダー]]で記述できます。
-
[60] 
[[プッシュメッセージ購読集合資源]]の [[URL]] を、
[[リンク関係型]]
[CODE[urn:ietf:params:push:set]]
で記述して[MAY[構いません]]。
[SRC[>>1 4.1.]]
[CODE[Link:]] [[ヘッダー]]で記述できます。
-- [18] [[要求]]で[[プッシュメッセージ購読集合資源]]が指定されていれば、
同じものを返す[SHOULD[べきです]]。
それがかなわない場合、新しいものを返して[MAY[構いません]]。
[SRC[>>1 4.1.]]

[63] 
[[本体]]については何ら規定がありません。
すべての情報は [[HTTPヘッダー]]で記述されるので、
省略し無視するのが適当と思われます。

[62] 
[[プッシュサービス]]は、負荷対策のため他の[[サーバー]]に分散させられます。
[[利用者エージェント]]は、
[CODE[307]]
[[応答]]に対応しなければ[MUST[なりません]]。
[SRC[>>1 7.1.]]
明記されていませんが、
[CODE[Location:]]
で[[リダイレクト]]された [[URL]]
に [CODE[POST]]
し直すことが期待されていると思われます。
[[同じ起源]]とは限らない (違う[[サーバー]]なので違う可能性も高い)
と思われますが、
任意の外部サーバーへの[[リダイレクト]]を認めてよいのか、
[[利用者エージェント]]の設計上注意が必要かもしれません。


[65] 
その他、
明文規定はありませんが、当然、[[利用者エージェント]]はエラー処理が必要です。



* 実装


[74] [[Chrome]] は違う
[CODE[applicationServerKey]] で既に subscribe 済みなら[[拒絶]]します・
[TIME[2019-01-04T09:18:31.800Z]]

[72] [[Chrome]] は 
[CODE[userVisibleOnly]] が[[偽]]なら[[拒絶]]します。
[TIME[2019-01-04T09:18:55.600Z]]



[55] 
[[Chrome]] でなぜか
[CODE[subscribe]]
[[メソッド]]の [[Promise]]
が解決されなくなることがあります。
なぜか全然応答しなくなり、固まっているようにみえます。
[[通知]]の許可を変更したり、リセットしたりしても解決しません。
[[Chrome]]
を再起動するとあっさり治ります。
[TIME[2019-09-30T06:54:07.400Z]]

* メモ