Apps」(PWA)という技術を提唱してその普及を推進している。PWAはネイティブアプリケーションのように動作するWebアプリケーションであり、オフラインでも動作し、プッシュ通知などの機能も利用できる。本記事ではこのPWAの中核となる技術の解説と、PWAに対応したWebアプリケーションを作成するための流れを紹介する。Apps」(PWA)とは一昔前は「ネットサービス」といえばPCのWebブラウザからアクセスして利用するものがほとんどだった。スマートフォンが普及した昨今では多くのサービスがスマートフォン向けの対応を行っている。今ではPCからのアクセスよりもスマートフォンからのアクセスのほうが多いサービスは珍しくなく、スマートフォン向けの専用アプリを用意しているサービスも多い。いえ、ネイティブアプリ開発にはWebアプリケーションとは異なるプラットフォーム固有の知識が必要となる。Webアプリケーションと同じ技術を使ってネイティブアプリを開発できるフレームワークなどもあるが、それでもいくつかの追加工程は必要となるほか、プラットフォームごとの制約もあり、アプリストアでの配布を行うためにはプラットフォームの審査も必要となる。Webアプリケーションの強みとしては、OSを問わずさまざまなプラットフォームで動作する点がある。画面サイズやWebブラウザごとの挙動の違いに対する配慮こそ必要だが、基本的には同一のコードでPCでもスマートフォンでもタブレットでも動作するアプリケーションを作成できる。昨今ではWebブラウザの機能拡張が活発に行われており、たとえばWebブラウザから端末のカメラやマイク、各種センサなどにアクセスするAPIや、端末内にデータを保存するためのAPIも導入されている。これらを利用することで、Webアプリケーションでもネイティブアプリと比べて遜色ない機能が実装できるようになっている。Webアプリケーションにはネットワーク回線が利用できない場所では利用できないという弱点があるが、これを解決するための技術も登場した。Androidでは端末のホーム画面からWebアプリケーションを起動する仕組みも導入されている。Apps」(PWA)と呼ぶ。docsでは次のように解説されている。independent:オフラインや貧弱なネットワーク環境でも使用できるRe-engageable:新しいコンテンツがあるときに通知を送信できるこのうち「Discoverable」や「Linkable」については一般的なWebサイトも備える特徴であり、また「Safe」についてはSSL/TLSを用いたHTTPS接続によって実現される。「Responsive」や「Progressive」についても、昨今では多くのWebサイトで課題とされているもので、既存技術であるCSSメディアクエリや一般的なJavaScirptライブラリ/フレームワークなどで対応できるものだ。independent」、「Re-engageable」の3つと言える。Workers」、「Push」の3つの技術について解説していく。Manifest」と呼ばれている。これは特定の設定ファイルを用意してWebページ内の「link」タグでそのファイルを指定することで、デバイスのホーム画面にそのWebアプリにアクセスするためのアイコンを追加できるようにするものだ。これによって端末のホーム画面からネイティブアプリのようにWebアプリを起動することができる。この設定ファイルでは表示するアイコンやアプリケーション名のほか、カテゴリやアプリケーションの簡単な説明といった属性も定義でき、これらをWebアプリケーションカタログなどの用途に利用することも想定されているようだ。Workers」モバイル端末では状況によって通信回線の品質が大きく変化する可能性がある。地下などの場所では通信しにくくなるほか、基地局が整備されておらず圏外になるような場所もまだある。Workersは、指定したサイト(もしくはURL)へのネットワークアクセスに対してプロクシのように動作するJavaScriptプログラムをWebブラウザ内でバックグラウンドで実行するものだ(図1)。Workersでは指定したサイト内(もしくはディレクトリ以下)のネットワークアクセスを完全に「横取り」することができ、たとえば前回のアクセス時に取得したコンテンツを端末内にキャッシュしておいて次回以降のアクセスではそれを返すことができるほか、端末内で生成したデータをあたかもWebサーバーにアクセスして取得したかのように返すこともできる。「Re-engageable」は、「Push」という仕組みで実現できる。Workersと組み合わせて使用する。サーバーが提供しているデータに対し何らかの変更が発生した際にPushを利用してその旨をWebブラウザに通知し、その通知をトリガーにバックグラウンドでキャッシュを更新する、といったことが可能となる。PWAの問題点このようにWebアプリケーション開発において大きな可能性を持つPWAであるものの、サポートするサービスはまだ多くない。普及が進んでいない理由の1つに、Webブラウザ側でのAPIのサポート不足がある。Chromeは2015年にリリースされたバージョン40からサポートしているが、Safariでサポートされるようになったのは2018年にリリースされたバージョン11.1(iOS版は11.3)からだ。PushについてはSafariは未だに実装されていない(デスクトップ版はApple独自仕様のものが実装されている)。Manifestに関しては現在でも「実験的」という位置付けで、Androidではサポートが進んでいるものの、iOS端末では未知というステータスだ。いった状況であるため、開発時には対象とするWebブラウザがどのAPIをサポートしているかを確認しておく必要がある。Manifest」について解説していこう。こちらは非常にシンプルな仕組みで、JSON形式でWebアプリケーションの情報を記述したファイルを用意し、そのファイルのURLをHTML内のhead要素内に次のようなlinkタグで記述するだけで実現できる。href="<マニフェストファイルのURL>">なお、マニフェストファイルは「.json」もしくは「.webmanifest」(MIMEタイプは「application/manifest+json」)という拡張子に設定するのが一般的だ。仕様では「.webmanifest」という拡張子が規定されているようだが、現状は「.json」でも認識されるという。このマニフェストファイルで定義できる情報は次の表1のとおりだ。アプリケーションの言語。Manifestについてはまだ規格の策定中であり、Webブラウザによってサポートされる情報が異なるほか、今後これらが変更される可能性もある点には注意したい。「category」や「description」、「iarc_rating_id」、「lang」、「screenshot」といった属性はWebアプリケーションの配信ストアのようなサービスでの利用を前提としているようで、現状あまり意味をなさない。そのため、現状では指定しておいた方が良い属性は以下の6つに留まる。background_colordisplayiconsnameshort_nametheme_colorこれらを指定したマニフェストファイルは、次のようになる。現状でこの機能をサポートするWebブラウザは限られており、現状はAndroid端末でのみサポートされている。Manifestに対応したWebアプリケーションへのアクセス時にホーム画面に登録するためのポップアップが表示され、そこで「ホーム画面に追加」などのボタンをタップすることで、Webアプリケーションを起動するためのアイコンをホーム画面に追加できる。47以降ではnameおよびbackground_color、icons属性から自動的にアプリケーション起動時のスプラッシュスクリーンを表示する機能も提供されている。Workersについて説明していこう。WorkersはWebブラウザとWebサーバーの間に割り込む、プロクシのように動作するJavaScriptプログラムを実現するものだ。PWAにおいてはオフライン時にWebサーバーにアクセスせずにWebブラウザ内に保存された各種リソースをWebアプリケーションに返すために使われる。Workers」という、JavaScriptコードを非同期に実行させる技術をベースとして実装されている。Workersについても簡単に紹介しておこう。Workers多くのプログラミング言語では、プログラム中から別スレッドもしくは別プロセスを起動して並列処理を行う仕組みが用意されている。Workersはこれらに似た仕組みで、実行させたいコードを引数で指定してWorker()コンストラクタを実行することで、指定したコードをメインのスレッドとは別のスレッド(もしくはプロセス等)で実行できる。Workerとして実行されるコード(Worker()コンストラクタで指定したコード)は、その呼び出し元コード(Worker()コンストラクタを実行するコード)とは完全に独立して実行され、変数や関数定義などを共有しない点がある。Workerオブジェクトによって実行されるコードはDedicatedWorkerGlobalScopeというコンテキスト内で実行され、利用できる機能が制限される。Workersドキュメントで解説されているが、たとえばWebブラウザのWindowオブジェクトやDOMにはアクセスできない。Workerオブジェクトによって実行されるコードとその呼び出し元コードとの通信は、メッセージを送受信することで行える。Workerオブジェクトによって実行されたコード内でpostMessage()メソッドを実行すると、引数で与えたメッセージが実行元のWorkerオブジェクトに送信される。WorkerオブジェクトではaddEventListener()メソッドもしくはonmessage()プロパティを使って「message」イベントに対するイベントハンドラを登録しておくことで、メッセージ受信時に指定されたメッセージを引数として受け取ってコードを実行できる。逆に、WorkerオブジェクトからはpostMessage()メソッドでメッセージを送信できる。メッセージが送信されると、Workerオブジェクトによって実行されたコード内ではmessageイベントが発生し、こちらも同様にイベントハンドラを使うことで送信されたメッセージを受け取ることができる。Workersから派生したもので、基本的な特性は同じであるが、いくつかの点で違いがある。WorkersGlobalScopeというコンテキスト内で実行される。このコンテキスト内ではメッセージを送信するpostMessage()メソッドが利用できない一方で、コンテンツをキャッシュするために利用できるCacheStorage型のcachesオブジェクトなどがあらかじめ定義されている。Workersではコードを直接実行させるのではなく、ServiceWorker.register()メソッドを使って実行したいコードを登録することで処理を開始させる。Workersでは呼び出し元ページが閉じられても永続的に動作する。Workersは再起動する。Workersは常時バックグラウンドで動作しているわけではない。後述するイベントの発生時に必要に応じて起動され、コード内で定義されたイベントハンドラが実行され、イベントハンドラの実行が完了するとアイドル状態となる。WorkersではJavaScriptのPromise機構が多用されるため、使いこなすにはPromiseについての知識が必須となる。ドキュメントなどを確認して把握しておこう。Workersとしてコードを実行するには、実行するコードのURLを引数として与えてnavigatorオブジェクトのserviceWorker.register()というメソッドを実行する。Workersが利用できるかを判定できる。具体的なコードは次のようになる。Workersが利用できないときに実行する処理を記述}ちなみに、navigator.serviceWorker.register()では第2引数としてオプションが格納されたオブジェクトを渡すことも可能だ。Workersの適用範囲を示す「scope」オプションのみとなっている。Workersが動作するようになる。このオプションを省略した場合、navigator.serviceWorker.register()を実行しているページが存在するディレクトリが指定されたように振る舞う。navigator.serviceWorker.register()を実行すると、Webブラウザはまず第1引数で指定したURLがそのページと同じオリジン内で、かつscopeオプションで指定したスコープ(scopeオプションが指定されなかった場合はnavigator.serviceWorker.register()を実行したページと同じディレクトリ以下)にあるかどうかをチェックする。Workersの登録に失敗する。続いてURLからJavaScriptコードを取得してパース・実行する。このときリソースをロードできなかったり、JavaScriptとしてのパースに失敗したりした場合も登録に失敗する。JavaScriptコードの実行が完了すると、続けてそのコード内で「self」として参照されているオブジェクトで「install」イベントが発生する。Workersを動かすために必要な処理や静的リソースのキャッシュといった処理を実行する。Workersのインストールは行われない。Workersは実行を継続する。Workersは停止される。installイベントが完了すると、続いて「activate」イベントが発生する。Workersが停止したタイミングでこのイベントが発生する。Workersの更新に関連するような処理を実行するのが一般的だ。Workersに対して「fetch」イベントが発生するようになる。このイベントに対応するイベントハンドラではFetchEvent型のオブジェクトが引数として与えられ、このオブジェクトのrespondWith()メソッドを実行することで、そのリクエストに対する応答を行える。WorkersではPush関連や通知関連のイベントも定義されている(表2)。SyncManager.registerが呼び出されたまた、将来的には課金関連のイベントなども実装が検討されているようだ。Workersを実装した例を見てみよう。今回の記事で使用しているコードはhttps://osdn.net/users/hylom/pf/pwa_samplecode/scm/tree/master/で公開しており、手元にダウンロードもしくはクローンし、適当なWebサーバーで公開することで試せるようになっている。簡易的なWebサーバーとして動作するPythonコードも用意しており、ディレクトリ内で「make」コマンドを実行すればこのWebサーバーが8080版ポートで動作するようMakefileも用意した。ここで紹介するサンプルコード(「service_worker」)は、JavaScriptを使ってWebサーバーから非同期に「data.json」というファイルを取得し、そのファイル中「data」というキーで定義されている文字列を表示するものだ。ページのロードだけでなく「更新」ボタンを押したタイミングでも同様の処理が実行される(図2)。Workersの登録もしくは登録解除ができるようになっている(図3)。Workersが利用できないWebブラウザの場合はどちらも表示されない。Workers」項目で確認できる(図4)。Workerが介入し、適宜キャッシュを返すようになる。Chromeのデベロッパーツールの「Network」タブでネットワークを「Offline」に設定した場合でも、ページをロードできるようになる(図5)。デベロッパーツールでは「Network」タブでオンライン/オフラインを切り替えられるこのサンプルコードでは、オフライン時にはdata.jsonへのアクセスについてはキャッシュではなく、「offline(<ランダムに生成した数字>)」という文字列を返すようにっており、「更新」ボタンを押すたびに表示される数字が変わることを確認できる。Worker内で処理が実行されていることが確認できる(図6)。オフライン状態でもページをロードしたり、「更新」ボタンによるデータ更新が行えるさて、このページのHTMLファイル(index.html)は次のようになっている。Workerはインストールされていません。Workerはインストール済みです。src="./index.js"></script></html>また、主要な処理はindex.jsというファイルで実装されている。Workerの登録状態をチェックする処理を行う「checkServiceWorkerRegistered()」関数と、data.jsonを取得してそのデータをページ内に表示する「updateStatus()」関数を実行するよう、windowオブジェクトに対しイベントハンドラを設定している。WorkersはWebブラウザの挙動を変化させるものであるため、今回のサンプルコードのように登録時はユーザーにその旨を確認し、了承を得た上で登録させるのが望ましい。Workersがインストールされているかどうかは、navigator.serviceWorker.getRegistration()メソッドで確認できる。WorkersがインストールされていればServiceWorkerRegistrationオブジェクトを、そうでなければundefinedを返すPromiseを戻り値として返す。Workersが登録(インストール)されていればアンインストール、登録されていなければインストールするためのボタンを表示する「checkServiceWorkerRegistered()」という関数を実装している。ここでは単にボタンを表示するだけで、ボタンをクリックしないと実際の処理は行われない。});}updateStatus()関数は、「./data.json」というURLに対しGETリクエストを送信し、受け取ったデータをJSONとしてパースしてその内容をページ内に表示する処理を行っている。APIを使ってリクエストを送信している。});}updateStatus()関数内で呼び出されているupdateStatusText()関数は、ページ内で「browser_status」というIDが付けられた要素を取得し、その要素内のテキストを引数で与えられたテキストに置き換える処理を行っている。Workersが登録されるようになっている。「インストール」ボタンのクリック時に実行されるのが次のregisterServiceWorker()関数だ。Workersとして登録している。}}また、「アンインストール」ボタンをクリックした際に実行されるunregisterServiceWorker()関数は次のようになっている。ここではnavigator.serviceWorker.getRegistration()で得られるServiceWorkerRegistrationオブジェクトのunregister()メソッドを使って登録解除を実行している。Workersを登録したページでネットワークアクセスが発生した際に発生する「fetch」イベントに対するイベントハンドラを定義する。Workersとして実行されるコードがパースされた後に呼び出されるイベントだ。今回のコードでは、ここでcachesオブジェクトを使ってWebページに必要なHTMLファイルやJavaScriptファイル、画像ファイルなどをWebブラウザ内にキャッシュしている。);});installイベントに対するイベントハンドラには引数としてInstallEvent型のオブジェクトが渡される。イベントハンドラ内では、このオブジェクトのwaitUntil()メソッドを実行することで処理の成功/失敗を判断するようになっている。waitUntil()メソッドは引数としてPromiseオブジェクトを取り、このPromiseオブジェクトが解決されれば成功、リジェクトされれば失敗となる。fetchイベントに対するイベントハンドラには引数としてFetchEvent型のオブジェクトが渡され、このオブジェクトのrespondWith()メソッドを使って処理結果を返すようになっている。今回は次のようにリクエストのURLをチェックして異なる結果を返すよう実装した。「/data.json」へのリクエストの場合:JavaScript内でレスポンスを生成して返す「/」へのアクセスの場合:index.htmlのキャッシュを返すキャッシュされているパスへのリクエストの場合:キャッシュを返すそれ以外のリクエストの場合:リクエストURLに対しネットワークリクエストを行ってその結果を返すいずれの場合も、ページ内のJavaScriptコードからはネットワーク経由でそのコンテンツを取得したかのように扱われる。具体的なコードは次のようになる。);});なお、このサンプルコードではself.navigator.onLineプロパティをチェックして、単純にオンライン/オフラインで挙動を切り替えているが、たとえばオンラインでも回線が遅いといったケースや、毎回キャッシュされたコンテンツを返したい、というケースもあるだろう。その辺りは状況に応じてカスタマイズする必要がある。いったキャッシュ処理を簡潔に記述するための「Workbox」というライブラリも公開されているので、検討してみると良いだろう。Push機能を実装する続いてはPush機能について説明していこう。前述のとおり、PushはWebサーバーからWebブラウザに対して非同期でメッセージを送受信できる機能だ。Workersで「push」イベントが発生し、イベントハンドラが実行される。そのため、メッセージを受信する際にはページを開いている必要はない。現行のPushの実装では、Pushを使ったメッセージの送信者はWebサーバーとは必ずしも一致する必要はない。Server」(アプリケーションサーバー)などと呼ばれている。アプリケーションサーバーは、Webブラウザ上でPush購読処理を行った際に得られるエンドポイント(Webブラウザの提供者が運営しているPushメッセージ送信サービスのURL)に対し、送信したいメッセージや送信先といった情報を含むリクエストを送信することでメッセージを送信する。ID(サブスクリプションID)などを含むPushSubscription型のオブジェクトが返されるようになっている(図7)。これらの情報をアプリケーションサーバーに送信し、保存しておく。Push購読処理の流れPushでメッセージを送信する際には、このエンドポイントに対してサブスクリプションIDとともにメッセージを送信する。Pushメッセージ送信用サーバーは受け取ったサブスクリプションIDやメッセージを検証し、それを対応するWebブラウザに転送する(図8)。Pushメッセージ送信処理の流れただ、単にエンドポイントとサブスクリプションIDのみを使うだけでは、たとえばエンドポイントに対しランダムに生成したIDでメッセージを送りつけるといったような攻撃も可能になってしまう。エンドポイントが攻撃を受けて乗っ取られるようなケースが発生した場合、通信内容が傍受される懸念もある。そのため現在のPushの実装では公開鍵暗号を使ってメッセージの送信元を検証するための「VAPID」という仕組みや、データをエンドツーエンドで暗号化して送受信する仕組みが導入されている。これら認証・暗号化の仕組みやメッセージ送受信の流れはMozillaのブログで紹介されているが、やや複雑だ。そのため詳細については割愛するが、流れとしては以下のようになる。アプリケーションサーバー(Pushメッセージの送信元)であらかじめ256ビットの楕円DSA(ECDSA)を使った公開鍵と暗号鍵を作成しておくWebサーバーはアプリケーションサーバーの公開鍵をWebブラウザに送信し、これを受け取ったWebブラウザはこれらの情報を引数としてPushManager.subscribe()メソッドを実行するWebブラウザはPushManager.subscribe()メソッドの戻り値に含まれるエンドポイントや認証トークン、エンドツーエンド暗号化に使用する鍵をアプリケーションサーバーに転送する。Workersのpushイベントを発生させるこれらの処理はやや複雑なので、あらかじめこれらが実装されたライブラリを使うことをおすすめする。いったライブラリはいくつかが存在するが、今回はGitHubのweb-push-libsリポジトリで公開されているものを紹介する。web-push-libsでは、アプリケーションサーバーの公開鍵/秘密鍵を生成するためのvapidというライブラリや、Node.jsやPython、Java、C#、PHPでPushによるメッセージ送信を行うためのライブラリが公開されている。web-push-libsのvapidツールを使った公開鍵の準備Pushを使用してメッセージを送信するには、前述のとおりアプリケーションサーバー側で公開鍵・秘密鍵を生成しておく必要がある。これは、web-push-libsのvapidライブラリで行える。vapidライブラリではJavaScriptおよびPython、Rust向けのコードが提供されている。3系ならpip3)経由でインストールできる。py-vapidこのパッケージをインストールすると、「vapid」コマンドが利用可能になる。Linux環境において非rootユーザーで実行した場合、通常インストール先は~/.local/bin以下になる。このコマンドを「–gen」オプション付きで実行すると、実行したディレクトリ内に公開鍵(public_key.pem)と秘密鍵(private_key.pem)が生成される。public_key.pemまた、作成した公開鍵/秘密鍵が保存されているディレクトリで「–applicationServerKey」オプション付きでvapidコマンドを実行すると、BASE64でエンコードされたアプリケーションサーバー鍵が表示される。←BASE64でエンコードされたアプリケーションサーバー鍵文字列Webアプリケーションでは、PushManager.subscribe()を実行する際にこのアプリケーションサーバー鍵文字列をオプション引数として与えれば良い。前述のようにweb-push-libsではいくつかの言語向けにPushによるメッセージ送信を行うライブラリを提供しているが、今回はPython向けのpywebpushを使ってみよう。pywebpushは、pipコマンド(もしくはpip3コマンド)で次のようにインストールできる。pywebpushpywebpushをインストールすると、同時に同名(「pywebpush」)のPushでメッセージを送信するためのコマンドがインストールされる。このコマンドでは、次のようにしてメッセージを送信できる。<アプリケーションサーバーの暗号鍵>「–info」オプションで指定する「PushManager.subscribe()が返した情報を格納したJSONファイル」というのは、次のようにPushManager.subscribe()が返すPushSubscriptionオブジェクトのtoJSON()メソッドを実行することで得られる。});なお、toJSON()メソッドが返すのはあくまで文字列ではなくオブジェクトなので、JSON文字列に変換するにはこのようにJSON.stringift()メソッドを実行する必要がある。「–claims」オプションで指定する『「claim」情報を格納したJSONファイル』というのは、次のようなJSONファイルだ。"mailto:<送信者の連絡先メールアドレス>"}ただし、pywebpushではこのうち「sub」プロパティのみを指定すれば良い。次のようなJSONファイルを指定すれば良い。"mailto:<送信者の連絡先メールアドレス>"}「–key」オプションで指定するアプリケーションサーバーの暗号鍵は、vapidコマンドで生成したものを指定する。今回の例の場合、「private_key.pem」を指定すれば良い。Workerでは「push」イベントが送信され、このイベントに対応付けられたイベントハンドラが実行される。このイベントハンドラには引数としてPushEvent型のオブジェクトが渡され、そのdataプロパティで送信されたメッセージを取得できる。Pushの実装例それでは、Pushを実装するに当たって実際にどのような処理が必要なのかをサンプルコードで説明していこう。Workersのサンプルコードを一部修正したもので、Pushで送信されたメッセージをWebブラウザ内に保存して表示するというものだ。Workerをインストールするボタンが表示される。異なるのは、ページ上部に「メッセージ:」という表示がある点だ。ここでは、Webブラウザ内に永続的にデータを記録しておけるIndexedDBを使用して作成したデータストア内に格納されたメッセージを表示している(図9)。サンプルページではWebブラウザ内に保存したメッセージを表示するここで「インストール」ボタンをクリックすると、通知を表示する許可を求めるメッセージが表示される(図10)。通知の許可を求めるメッセージ今回のサンプルコードでは通知の表示は行わないが、Pushは通知表示にも使われるのでこのような許可が必要になるようだ。ここで「許可」をクリックするとPush購読が開始され、デベロッパーツールのコンソールにエンドポイントなどの情報がJSON形式で表示される(図11)。通知を許可すると、デベロッパーツールのコンソールにエンドポイントなどの情報が表示される本来はこの情報はネットワーク経由でWebサーバーに送信して保存するのだが、今回はサンプルコードということで、このJSON文字列をコピーしてローカルの「subscription.json」というファイルに保存する。この情報を使って、ローカルにインストールした「pywebpush」コマンドを次のように実行するとPushでメッセージが送信される。}」というデータを送信すると、Pushによってこれを受信したWebブラウザは次のようにこのメッセージを表示する(図12)。通知を受信すると、その通知内に含まれていたメッセージを表示する前述のとおりメッセージ受信はバックグラウンドで行われるため、このページを開いていなくてもメッセージの受信は実行できる。Webブラウザが実行されていない場合はメッセージは受信できない。実際のコード内容は次のようになる。テストページのHTMLファイル(index.html)は先のサンプルコードとほぼ同じだ。Workerはインストールされていません。Workerはインストール済みです。src="./index.js"></script></html>また、メインのスクリプトファイルのindex.jsだけでなく、IndexedDBに関する処理をまとめた「test-db.js」もscriptタグで読み込ませている。ここではIndexedDBのデータストアにメッセージを保存する「addMessage()」と、メッセージを読み出す「getMessages()」の2つのメソッドを定義している。IndexedDBについて詳細は割愛するので、詳しくはこのコードを直接確認してほしい。メインのJavaScriptコード(index.js)では、windowの「load」イベントに対するイベントハンドラで「インストール」および「アンインストール」ボタンへのイベントハンドラ設定、IndexedDBからのデータ読み出しおよび読み出したデータの表示といった処理を行っている。5000);});IndexedDBからのデータ読み出しおよび読み出したデータの表示処理はreloadMessages()という関数にまとめている。});}サーバー認証やメッセージの暗号化のためには、Push購読の際にアプリケーションサーバーの公開鍵を登録する必要がある。今回はweb-push-libのvapidコマンドを使って事前に生成しておいたアプリケーションサーバー鍵をソースコード中にハードコードして利用している。Workersの登録に加えて、Push購読のための設定も行う。Workersの登録処理については、先のサンプルコードの場合と変わらない。Push購読についてはnavigator.serviceWorker.register()メソッドなどが返すServiceWorkerRegistrationオブジェクトのpushManager.subscribe()メソッドを使用する。Workersの初期化が完了していないと実行できない。Workersがアクティブな状態になった際に処理を実行するようにしている。}}前述のとおり本来はここでsubscribe()メソッドの実行結果で得られるエンドポイントなどの情報をWebサーバーに送信して保存するべきだが、今回はその代わりにコンソールにそれらをJSON形式で表示している。Workersの登録解除を行っている。Push購読情報はPushManager.getSubcription()でPushSubscription型のオブジェクトとして取得できる。Push購読の解除は、このオブジェクトのunsubscribe()メソッドを実行することで行える。Workersとして実行されるsvc_worker.jsでは、先のサンプルコードをベースとしつつ、pushメッセージを受け取った際に発生するpushイベントのイベントハンドラを追加している。ここではまず、importScripts()メソッドでIndexedDB関連の処理を記述したtest-db.jsファイルを読み込んでいる。Workers内で別のJavaScriptファイル中に含まれているオブジェクトや関数を使用したい場合、このようにしてそのファイルを読み込ませる必要がある。test-db.jsを読み込むself.importScripts("./test-db.js");また、pushイベントのイベントハンドラは次のようになる。ここでは受信したメッセージを引数として渡されたイベントオブジェクトから取得し、IndexedDBのデータストアに保存する処理を行っている。Workersは、ただ単にページやコンテンツをキャッシュさせるだけであれば、このように比較的容易に実装できる。Workerを更新するタイミングなどの検討が必要となるだろう。Pushについてはセキュリティの関係上やや複雑な仕様になっているが、ライブラリを使用すればこちらも比較的容易に実装できる。現時点では同時に複数のユーザーを対象にメッセージを送信する仕組みがない点に注意したい。今回のサンプルではIndexedDBというデータ保存用APIを使用したが、それ以外にも最近のWebブラウザではさまざまなAPIが提供されている。APIといったものも用意されている。PWAについてはまだiOSデバイスでのサポートが不足しているという問題はあるが、今後のWebアプリケーション分野の発展に期待したい。