[PWA]プッシュ通知を実現するPushAPIとNotificationAPI

- この記事は[PWAアドベントカレンダー](https://qiita.com/advent-calendar/2020/pwa)の5日目の記事です

## Webアプリにおけるプッシュ通知

- PWAを代表する機能としてプッシュ通知があげられることがよくあると思います（iPhone/Safariだとまだ使えないんですがね）
- Webアプリにおけるプッシュ通知は[PushAPI](https://developer.mozilla.org/ja/docs/Web/API/Push_API)と[NotificationAPI](https://developer.mozilla.org/ja/docs/Web/API/Notifications_API)というの2つの仕組みからなっています

![スクリーンショット 2020-12-05 2.36.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/477e9833-6d19-1be2-49c0-715a7ce6462a.png)
![Screenshot_20201205-023648.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/a5987bf3-ca5c-5006-011a-72f67f411e55.png)

### PushAPI

- PushAPIはサーバからの通知を受け取ることができるAPIです

### NotificationAPI

- NotificationAPIはユーザに対して通知を送ることができるAPIです

### 一連の流れ

- Webアプリにおけるプッシュ通知送信までの一連の流れは、PushAPIがサーバからの通知を受け取り、NotificationAPIを呼び出してユーザに通知を送ります

![スクリーンショット 2020-12-05 2.12.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/ad5f30ae-bd9e-1253-8f59-126af53a14f9.png)

- それぞれの使い方を整理しようと思いましたが思いの外大変だったので今回はNotificationAPIを紹介します

## NotificationAPIの使い方

- NotificationAPIはユーザに通知を送るAPIでした
- 通知を送るためには事前に許可を得る必要があります
    - 見た瞬間に拒否を押してしまうあれですね・・・（許可を得るタイミングはよく考えて実装しましょう！）

![スクリーンショット 2020-12-05 2.16.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/867ef5ab-33e6-2a07-cb62-50b82b942487.png)

- というわけで**許可をとるフェーズ**と**通知を送るフェーズ**の2つに分けて説明していきます

### 許可を取るフェーズ

- 通知の許可を得るためには`Notification.requestPermission()`を実行することでユーザに尋ねることができます
- `requestPermission()`はPromiseを返し、ユーザが 許可 or ブロック を選択するとresolveされて結果を取得できます
    - 許可なら`granted`、ブロックなら`denided`という値を取得できます
- 以下のコードで試すことができます

```html
<button id="confirm">通知の許可を取得する</button>
<script>
  document.getElementById('confirm').addEventListener('click', () => {
    Notification.requestPermission().then(permission => {
      alert(permission); // granted or denied
    });
  });
</script>
```

- 現在の許可状態を確認することができる`Notification.permission`というものもあります
- 許可状態は`default`（未回答）、`granted`(許可済み)、`denied`(ブロック)の3つのいずれかが返ります

```js
const permission = Notification.permission;
console.log(permission); // default, granted or denied
```

### 通知を送るフェーズ

- 許可を得ると通知を送信できるようになります
- 以前は`new Notification('通知の内容')`でも送信できましたが手元のAndroidではエラーとなり、[ちょっとググってみたところこのやり方は非推奨で](https://stackoverflow.com/questions/29774836/failed-to-construct-notification-illegal-constructor#answer-29915743)以下で紹介するServiveWorkerから送るやり方が推奨されるとのことです
- メッセージの送信はServiveWorker上で`self.registration.showNotification('通知の内容')`といった感じで送信できます
- 以下のコードは動作確認しやすいようにブラウザからServiceWorkerに`postMessage`でメッセージを送り、ServiceWorkerはそれを受け取ってプッシュ通知を送っています

```html:index.html
<button id="send">通知を送信する</button>
<script>
  // ServiceWorkerの登録
  window.addEventListener('load', () => {
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker
        .register('sw.js')
        .then(registration => console.log('registered', registration))
        .catch(error => console.log('error', error));
    }
  });

  // 通知の送信
  document.getElementById('send').addEventListener('click', () => {
    if (Notification.permission === 'granted') {
      navigator.serviceWorker.ready.then(registration => {
        registration.active.postMessage('hello!!!');
      });
    }
  });
</script>
```

```js:sw.js
self.addEventListener('message', function (event) {
  self.registration.showNotification(event.data);
});
```

## サンプル

- 上で紹介した記事をベースに作成したサンプルはこちらです
    - http://push-notification-sample.vercel.app/
- ソースコードはこちらです
    - https://github.com/ozaki25/push-notification-sample

## まとめ

- 調べながらだと時間かかりましたが自前でも十分実装できるレベル感ですね
- PushAPIの部分も時間があればまた調べたいと思います