WWDC Quick Look 💓 By SwiftGGTeam
Learn more about Declarative Web Push

Learn more about Declarative Web Push

元の動画を見る

ハイライト

Declarative Web Push は push メッセージに web_push: 8030 という識別キーを付与することで、ブラウザが Service Worker の JavaScript を実行することなく直接通知を表示できるようにする仕組みです。古いブラウザに対しても後方互換性を保ちます。


主要内容

ネイティブ app では、デバイス上にコードを書いて通知を表示する必要はありません。通知の内容は標準フォーマットで push メッセージに記述されており、システムがそれを読み取って表示するだけです。iOS によってクラウドへオフロードされた app であっても、通知は問題なく届きます。一方 Web ではひと手間必要で、push が届くたびに Service Worker を起動し、開発者が書いた JavaScript で JSON を解析し、showNotification() を呼び出すという流れになります。つまり、メンテナンスが必要なコードを余分に書き、CPU と電力を消費するということです。さらに悪いことに、Intelligent Tracking Prevention はプライバシー保護の観点から Service Worker の JavaScript の生存時間を制限するため、push がそもそもユーザーに届かないケースもあります。

WebKit チームはあることに気付きました。多くのサイトの push メッセージは結局のところ単純な JSON であり、Service Worker のロジックも各フィールドを showNotification() の引数に詰め直しているだけだということです。これだけ一般的なパターンであれば、いっそブラウザ側に処理を任せてしまえばよいわけです。Declarative Web Push の核心は、push メッセージに "web_push": 8030 を付けるだけで、ブラウザは JSON 全体を標準的な通知記述として扱い、Service Worker を起動することなく自動で通知を表示するという約束事にあります。Safari 18.5(macOS)および iOS 18.4 / iPadOS 18.4 以降の「ホーム画面に追加」した web app で既にサポートされています。

後方互換性こそ、この設計の心地よさの源です。ブラウザが magic key を認識しなければ、従来の Web Push にフォールバックします。JSON のパースに失敗した場合もフォールバックします。JSON は正しいが有効な通知を記述していない場合は、ブラウザはそれを単に破棄します。クライアント側でエンドツーエンド暗号化や未読数の補正など、どうしても処理が必要な場合は、JSON に "mutable": true を加えれば、Service Worker でオプショナルな後処理が行えます。処理に失敗したら、宣言的 JSON 内の平文の通知へフォールバックする仕組みです。


詳細

従来の Web Push のフローは Service Worker に依存しています。まず worker を登録し、pushManager でサブスクリプションを申請し、push が届いたら worker が push イベントを受け取って showNotification を呼ぶ、という流れです(04:41)。Declarative Web Push は、サブスクリプション申請以外のコードをすべて削ぎ落とします。サブスクリプションの入口も ServiceWorkerRegistration.pushManager から window.pushManager へ昇格しました。Service Worker が必須ではなくなったためです(07:54)。

最小構成の宣言的 push メッセージは次のような形になります(08:14)。

{
  "web_push": 8030,
  "notification": {
    "title": "Hello from Browser Pets",
    "navigate": "https://browserpets.example/inbox"
  }
}

ポイント:

  • "web_push": 8030: magic key で、値は 8030 固定(IETF Web Push の RFC 番号)です。ブラウザはこのキーを見て宣言的経路を通すかどうかを決定します。見つからなければ従来の Web Push にフォールバックします。
  • notification: 必須フィールドで、ユーザーに見える通知を記述します。
  • title: 通知のタイトル、必須。
  • navigate: 通知をタップしたときに開く URL、必須。これが宣言的通知の最小有効形です。

すべてのフィールドは W3C の NotificationOptions 辞書をそのまま流用しているので、body・tag・サウンド・アプリバッジなども載せられます(10:08)。

{
  "web_push": 8030,
  "notification": {
    "title": "Hello from Browser Pets",
    "navigate": "https://browserpets.example/inbox",
    "body": "You have a new direct message",
    "tag": "dm",
    "silent": false
  },
  "app_badge": "3"
}

ポイント:

  • body: 通知本文。Notification.options.body と同じ意味です。
  • tag: 通知のマージ・置換用の識別子。ネイティブの NotificationOptions.tag と同義。
  • silent: false: プラットフォームに既定の通知サウンド再生を要求します(システムポリシーの影響を受けます)。
  • app_badge: 宣言的 push にビルトインされたアプリバッジ更新。Service Worker から個別に Badging API を呼ぶ手間を省けます。

クライアント側で復号や内容の修正が必要な場合は "mutable": true を指定します。すると、ブラウザはこの push を Service Worker にディスパッチし、イベントに「提案中の通知」を添えてくれます(12:45)。

self.addEventListener("push", (event) => {
  const proposed = event.proposedNotification;
  const data = event.data.json();
  const decrypted = tryDecrypt(data.encrypted);
  if (decrypted) {
    event.waitUntil(
      self.registration.showNotification(decrypted.title, decrypted.options)
    );
  }
  // showNotification を呼ばなかった場合、ブラウザは proposedNotification をフォールバックとして使う
});

ポイント:

  • event.proposedNotification: mutable フローにおける宣言的 JSON の「元の提案」。Service Worker はこれを読んで、たとえば DM かどうかを判定できます。
  • tryDecrypt(...): ローカルの鍵で push の暗号化 payload を復号します。鍵を持っているデバイスだけが平文を取り出せます。
  • showNotification(...): 復号に成功したら、本物のテキストで提案を差し替えます。ブラウザは差し替え版を優先表示します。
  • showNotification を呼ばなかった場合: 復号失敗、worker の起動失敗、リソース不足などの状況では、ブラウザが宣言的 JSON 内の平文通知を自動的にフォールバックとして表示します(14:01)。

Browser Pets の移行サンプルは改修ルートをよく示しています(15:36)。元々は ad-hoc に積み上げられた JSON フィールド(titleclickURLbodysilent など)を、NotificationOptions の標準フィールドへ一つずつリネームし、magic key を加えるだけです。新しいブラウザは即座に宣言的経路に乗ります。古いブラウザは引き続き Service Worker 経由ですが、worker のコードは showNotification(data.notification.title, data.notification) の一行に簡略化でき、data.clickURL のような手書きパースは不要になります。


重要ポイント

  • やること: 既存の ad-hoc な push JSON を NotificationOptions 標準フィールドにリネームし、"web_push": 8030 を加える。

    • なぜやる価値があるか: 新しいブラウザはすぐに宣言的経路に乗るため、Service Worker の起動が一回減り、省電力で、ITP に阻まれにくくなります。古いブラウザは自動でフォールバックするのでリスクゼロです。
    • 始め方: まずサーバ側の push メッセージ生成箇所でフィールドのマッピング(例: clickURL → navigatetext → body)を行い、JSON のトップレベルに magic key を差し込みます。
  • やること: Service Worker の push handler を「notification フィールドを取り出して showNotification にそのまま転送する」という形に簡素化する。

    • なぜやる価値があるか: 宣言的 JSON の notification サブオブジェクトはそれ自体が正当な NotificationOptions なので、フィールドを再構築する必要がなくなり、バグの発生面積が大幅に縮小します。
    • 始め方: push イベントで event.data.json().notification を読み取り、title を取り出し、残りのオブジェクト全体を options として showNotification に渡します。
  • やること: エンドツーエンド暗号化や未読数補正など、ローカル処理がどうしても必要な場合に限り "mutable": true を設定する。

    • なぜやる価値があるか: デフォルトはブラウザに自動表示させることで、Service Worker が ITP によって整理されたり、デバイスのリソースが逼迫したりした状況でも通知を届けられます。必要なときだけ mutable フローを使う方針です。
    • 始め方: 既存の push 種別を棚卸しし、「純粋に表示するだけ」と「ローカルでの復号・補正が必要」を分類して、前者は完全に宣言的に、後者は mutable + Service Worker をフォールバック付きで残します。
  • やること: app_badge フィールドでアプリバッジ更新と通知をひとつの push にまとめる。

    • なぜやる価値があるか: worker の起動と Badging API 呼び出しを一回減らせる上に、バッジと通知をアトミックに同期できます。
    • 始め方: サーバ側で push を組み立てる際、「未読数」を直接 app_badge に書き込み、バッジ更新だけのための空の push を別途送らないようにします。

関連 Session

コメント

GitHub Issues · utterances