create-react-appにpush通知を受信するservice workerを追加する

# Push通知やってみたい！！！ :triumph: 

けどkotlinもswiftも書けないしな〜 :cry:
ブラウザにしてもwebsocket？なにそれ食えるの :baby:
インフラエンジニアの自分には厳しいかな :thinking:
...
等思ってたらなんとservice workerというものを使えば簡単にブラウザで試せるという噂をキャッチ

しかもcreate-react-app(cra)でテンプレを作ればservice workerのボイラープレートもくっついてくるっぽいのでこれを使うことにしました

なお筆者はフロントエンドに造詣が全くないですので意味・単語等間違いがありましたらご指摘ください。

### 環境

- linux
    - chromium(72.0.3626.119)
    - firefox dev edition(66.0b12)

### 成果物

https://github.com/kentac55/rca-sw-push-ts

## step by step

### 登場人物

- フロントエンド(react)
- バックエンド(適当なwebサーバー)
- pushサービス(firefoxならmozilla, chromiumならgoogleになります)

### いつもの依存モジュールのインストール

ｶﾀｶﾀしていると安心するのでtypescriptにてお送りします

eslintはcraに入っています(最新版入れると`yarn start`時に怒られます)

```bash
yarn create create-react-app pj --typescript
cd pj
yarn add -D @typescript-eslint/eslint-plugin @typescript-eslint/parser @typescript-eslint/typescript-estree esli
nt-config-prettier eslint-plugin-prettier eslint-plugin-react husky lint-staged
```

### ServiceWorkerの登録

[commit](https://github.com/kentac55/rca-sw-push-ts/commit/de7a0d29c9f43b9eba631e39c729f7510c256315)

そのままだとprecacheしかやってくれませんので、通知を受信できるように編集します

`src/serviceWorker.ts` に3点追記します

- `applicationServerPublicKey`
    - [web-push](https://github.com/web-push-libs/web-push)使って生成しておきます
- `urlB64ToUint8Array`
    - base64をuint8arrayに変換するヘルパー関数です
    - https://github.com/GoogleChromeLabs/web-push-codelab/blob/master/app/scripts/main.js#L31
      を参考にts化します
- `registration.pushManager.subscribe({})...`
    - pushManagerにsubscribe情報を登録します
    - pushサービスからエンドポイント情報が返ってくるのでそれをバックエンドに通知します
    - この際、デフォルトのままだと型定義がが無いと怒られるのでtsconfig.jsonのlibに`webworker`を追記します

### ServiceWorkerの有効化

[commit](https://github.com/kentac55/rca-sw-push-ts/commit/b288fb0d257bf8b76cb0ec49f9e8f22d601a1a42)

デフォルトではserviceworkerのregisterが切られてますので有効化します
`src/index.tsx`の`serviceWorker.unregister()`を`serviceWorker.register()`に変更します
こいつを有効にしてもproductionなenvじゃないと動きません([reactのdocument](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)にもbuildして試してみてねと書いてあります)

### 通知ボタンの配置

[commit](https://github.com/kentac55/rca-sw-push-ts/commit/c745bbc39133920f0cc18c4994f3b1f6965a393b)

`App.tsx`にコンポーネントを配置します。イメージとしてはポチると通知が飛ぶ感じですね
デバッグ時にめんどくさいのでservice workerのunregisterボタンも用意しておきます

### swの差し替え

このままでもserviceworkerは動きますが、[デフォルトのwebpack.config.js](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/config/webpack.config.js)はworkboxのGenerateSWによってserviceworkerを生成しているため、precache機能しか発揮してくれません
というわけで自作のswに差し替えちゃいましょう

#### react-app-rewiredの追加

[commit](https://github.com/kentac55/rca-sw-push-ts/commit/3e910d96c303cecf18e0f8c4cc60b5950f71107f)
[参考](https://medium.freecodecamp.org/how-to-build-a-custom-pwa-with-workbox-in-create-react-app-be580686cf73)

`yarn eject` を行って出てきたwebpack.config.jsを書き換えても良いですが不可逆的変化怖い... :chicken: 
故にcraのconfigを上書きできる`react-app-rewired`を使います

```bash
yarn add -D react-app-rewired
```

`config-overrides.js`に書いていきます

#### 自作service workerを書く

[commit](https://github.com/kentac55/rca-sw-push-ts/commit/3e910d96c303cecf18e0f8c4cc60b5950f71107f)

本当はtypescriptで書きたかったけど、webpack力がないので断念 :sob:  

### バックエンドを書く

[otiai10さんの記事](http://otiai10.hatenablog.com/entry/2017/06/19/200715)を参考に実装しました
やったことはtypescript化したことくらいなので省略します

### 動かす

```bash
yarn build && serve -s build
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/73816/bfa3d75b-4b7c-6737-647d-3e0ff6a9458f.png)

:thinking: oO(なぜかchromiumはアイコンが変わらない・・・)

## 所感

- serviceworkerをtypescriptで書けなかったの悔しい！！！
- serviceworker自体はドキュメントが充実している事もあってさくっと作れた
- 微妙にchromimとfirefoxで挙動が違った(エラーとか)のに苦労した(chromiumの方がエラー多かった印象)
- ブラウザのdevtoolというのを初めて触ったが、以下の点でchromiumの方が使いやすかった
    - Javascript contextを指定できる(例えばsw.jsにするとselfがServiceWorkerGlobalScopeになる)
    - Service Workerのunregisterをdevtoolで行える(firefoxは`about:serviceworkers`)
        - 当初[この記事](https://dev.mozilla.jp/2016/03/debugging-service-workers-and-push-with-firefox-devtools/)に従って`about:debugging#workers`を使用したが、なぜかこのページを開いているだけでCPU負荷が高騰してFirefox全体がフリーズするまでになってしまった
            - warningにも書かれていたが、multiple content processesとの併用が原因なのかもしれない
    - pushサービスのendpointのurlが短い(気持ちの問題)
- フロントエンドすごい(特にエコシステム)

## その他備忘

- serviceworkerはhttps経由もしくは通知元がlocalhostの場合のみ有効になる
- typescriptでservice-workerを書く時は[ひと工夫必要](https://github.com/Microsoft/TypeScript/issues/14877#issuecomment-340279293)

## 参考文献

- [Making a Progressive Web App · Create React App](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
- [Push and Retrieve Payload Recipe - Web Push - ServiceWorker Cookbook](https://serviceworke.rs/push-get-payload.html)
- [webブラウザにPush通知送るサーバとjsのサンプル - DRYな備忘録](http://otiai10.hatenablog.com/entry/2017/06/19/200715)
- [Push API - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/Push_API)
- [Workbox webpack Plugins  |  Workbox  |  Google Developers](https://developers.google.com/web/tools/workbox/modules/workbox-webpack-plugin)
- [How to build a custom PWA with Workbox in create-react-app](https://medium.freecodecamp.org/how-to-build-a-custom-pwa-with-workbox-in-create-react-app-be580686cf73)