Angular Web Pushでクリックをハンドリング

# 概要
AngularでWEB pushを受け取った際に、通知をクリックされた時の処理をどこに書けば良いのか分からなかったので、まとめました。

# 通知内容
以下のようなペイロードをサーバー側から送るとします。これらのパラメータの説明は[こちら](https://developer.mozilla.org/en-US/docs/Web/API/notification)にあります。

```json
{
	"notification": {
		"title": "Push Sample",
		"body": "This is push test.",
		"icon": "assets/service-icon.png",
		"vibrate": [
			100,
			50,
			100
		],
		"data": {
			"url": "https://www.google.com/"
		},
		"actions": [
			{
				"action": "explore",
				"title": "Go to the site"
			}
		]
	}
}
```
今回は通知をクリックしたら`data.url`のURLのページが新たに開かれることを目標にします。

# 正攻法編(notificationClicks)
まずは[公式ドキュメント](https://angular.io/api/service-worker/SwPush)通り進めます。`notificationClicks`で通知クリックをハンドリングして通知内容のURLにリダイレクトします。

```app.component.ts
import { Component } from '@angular/core';
import { SwPush } from '@angular/service-worker';
import { Router } from '@angular/router';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
  providers: [],
})
export class AppComponent {
  constructor(private swPush: SwPush, private router: Router) {
    this.swPush.notificationClicks.subscribe(
      ({action, notification}) => {
        router.navigate([notification.data.url]);  // 今開いてるタブで開く
        // window.open(notification.data.url);  // 新しいタブで開く
      });
  }
}
```

しかし、この方法ではアプリがフォアグラウンドにある時にしかイベントをフックできません。

# 魔改造編(ngsw-worker.js)
SPAのプロダクトとしてバックグラウンドで通知からアプリが開けないのは致命的です。そこで、先人達のようにサービスワーカー自体にイベントフックを仕込みます。Angularではビルド時にサービスワーカーが自動生成され、本来は開発者は触らなくて良い(触られることを想定していない)のですが、バックグラウンドでの動作を変えるためにはサービスワーカーを改変するしかないのです。

`ng build --prod`でビルドをすると`dist/{YourProject}`配下に`ngsw-worker.js`ができます。このファイルの`notificationclick `をフックしている部分を改変します。

## 改変前

```dist/{YourProject}/ngsw-worker.js
// 省略
this.scope.addEventListener('notificationclick', (event) => this.onClick(event));
// 省略
```

## 改変後

```dist/{YourProject}/ngsw-worker.js
// 省略
this.scope.addEventListener('notificationclick', (event) => {
    console.log('[Service Worker] Notification click Received. event', event);
    event.notification.close();
    if (clients.openWindow && event.notification.data.url) {
        event.waitUntil(clients.openWindow(event.notification.data.url));
    }
});
// 省略
```

改変後のコードによってプッシュ通知をクリックするとアプリを開いていなくても、`data.url`を新しいタブで開けるようになりました。

## 運用
この魔改造コードはビルドするたびに改変前のコードに上書きされてしまいます。この対処方としては、

1. 毎回コードを書き直す
2. 上書きしたサービスワーカーを用意しておき、毎回ファイルを置き換える
3. `node_modules/@angular/service-worker/ngsw-worker.js`を同じように改変する

が挙げられます。3を行うことで、ビルドしても改変後のコードでビルドされ、個人的には一度対処してしまえば終わりなので、楽で良いのですが、パッケージのバージョンを挙げた時に置き換わってしまう、他の開発者と差分が生まれる、`node_module`を書き換えてしまうという倫理的な問題など、問題が山積みです。

工数かけて綺麗にやるなら、2をCI/CDでいい感じにするというのが妥当かもしれません。

(そこまでするならAngularにプルリクを出した方が感もありますが、)
しかし、[この記事](https://medium.com/kabbage-engineering/angular-pwa-app-notification-using-firebase-cloud-messaging-d1b7bd171b98)ではAngularはこの問題を短期的には解決する気がないとも語られています。

# 最後に
もっと良いやり方があればコメントで教えていただきたい、、

# 参考
https://stackoverflow.com/questions/48972973/clicking-the-action-part-of-angular-service-worker-push-notifications
https://stackoverflow.com/questions/53810194/angular-7-pwa-swpush-push-notifications-not-working
https://angular.io/api/service-worker/SwPush#notificationClicks