Chienomi

ChienomiがPush APIによるウェブ通知に対応

開発::website

要望されてからだいたい5年越し。

「めんどくさい……」と後回しにしていたウェブ通知に対応した。

とてもめんどくさかったが、何が面倒って、概念が複雑すぎて理解するのが面倒なのだ。 さらに、MDNに具体例も何も載っていないため、それもつらい要因になっている。

本記事はその覚書である。 push APIや通知について、ちゃんと理解しているわけではないため、具体的な説明は省き、概念的な話と必要な手順に留める。

(Client) Push APIとNotification API

Push APIはサーバーからのpushでデータを受け取るためのAPI。

Notification APIは通知を表示するためのAPI。

セットで使うことが多いし、連動してもいるけれど、存在としてはセットではなく別々。

Chienomiの場合は通知を表示することが目的なので、セットで使う。

(Client) Service Worker

Pushされたときに走るスクリプトはService Workerとして登録される。

つまり、ブラウザで動くスクリプトとは別のファイルを用意する必要があるが、それはウェブブラウザで走るスクリプトではある。 ページを開いてなくても走るため、DOM操作とかはできなさそう。

そのServer Workerを登録するスクリプトはページのスクリプト側に必要になる。

(Client) Notification API

Notification APIの使用はユーザーの許可が必要。 ここは有名なやつ。

基本的には「サービスワーカーの登録→通知許可の確認→サブスクリプションの登録」という手順になる。

VAPID鍵

楕円暗号鍵が指定されている、非対称暗号鍵。

client側には公開鍵が、server側には公開鍵と秘密鍵の両方が必要。

生成方法は色々あるけれど、pushするのが結構大変で、ライブラリを使わない選択肢は厳しそうであるため、私はnpmパッケージのweb-pushを使ったため、鍵の生成もこのパッケージでyarn execした。

GCMAPI鍵

Push APIが今の形になる以前に使われていた、Googleスタイルの鍵っぽい。 特になくても機能する。

通知の仕組みの概要

  • clientは通知を許可し、サブスクリプションを獲得する
    • サブスクリプションサービスはChromeならGoogleに、FirefoxならMozillaにあるサーバー
  • その上でclientは獲得したサブスクリプションをサーバーに伝達する
  • サーバーはそのサブスクリプションを保存しておく
    • endpointが「そのブラウザのそのサブスクリプション」に固有になっている
    • pushは個別に送る。一斉送信はないので、一斉送信したい場合はイテレータを回す

サブスクリプションの終了

  • ブラウザで通知の許可を削除するとサブスクリプションも無効になる
  • この状態になるとendpointは410 Goneを返すようになる
    • サーバーとしてはこれをハンドルしてサブスクリプションを削除すれば良い
  • 通知を使わないサブスクリプションはどうなるのかは不明

サーバー

サーバーに求められる役割は

  • サブスクリプションを登録する
  • pushする (&無効になったサブスクリプションを削除する)

の2つ。

登録するほうはウェブブラウザからやるため、ウェブサーバーである必要がある。 pushするほうは別にウェブ経由でなくても良い。

Chienomiのサーバー側通知システム構成

Sinatraによるアプリケーションサーバーが動いていて、サブスクリプションの管理はこのサーバーが行っている。

で、このサーバーの使うデータベースがNDBMで、でもpushを楽にするためにはNode.jsでweb-pushパッケージを使いたいため、pushするときはRubyのスクリプトでNDBMデータベースを探索し、リクエストごとにNode.jsを呼んでpushし、410 Goneが返された場合はそのNode.jsスクリプトがSinatraのサーバーに対してHTTPリクエストを投げる、という方式になっている。

このため、アプリケーションサーバー, Rubyスクリプト, Nodeスクリプトの3つがある上に、アプリケーションサーバーをコントロールするためのSystemdユニットがあり、またアプリケーションサーバーへルートするようにNginxの設定も追加した。

なお、unsubscribeのAPIは外部には公開されていない。