FCMでWebプッシュをLinuxからスマホへ送る

## やりたいことの概要
+ Linux(Ubuntu)のBashスクリプト内で条件を満たした際にAndroidへプッシュ通知したい
+ 通知の本文は適宜変更したい
+ 個人のローカルPCから発信したい

<details><summary>この記事を書いた経緯</summary>

+ メール送信の方が手軽で情報も多いが、他のメールに埋もれたり削除するのが面倒
+ 2024年7月22日にAPIの変更があり新しい方法での紹介が少なかった
+ Node.jsに初めて触れてバージョン違いに苦戦したため

APIの移行方法については[公式webページ](https://firebase.google.com/docs/cloud-messaging/migrate-v1?hl=ja)に記載があるが、トークンの取得など色々と変更があり、FCMについて全く知らない状況からでは読み替えが難しかった。

</details>


## 環境
+ 送信元: Ubuntu 20.04.06 LTS (WSL2上)
+ 送信先: Google Chrome 133 (SH-M19, Android13)
+ nvm 0.39.7
+ npm 9.9.4
+ node v18.20.6

## 参考にした公式のサンプルコード
+ Chromeで端末のトークンを取得するのに利用
  https://github.com/firebase/quickstart-js
+ Bashからwebプッシュを送信するのに利用
  https://github.com/firebase/quickstart-nodejs

## 全体の流れ

1. サンプルコードのダウンロード
1. Node.jsのインストール（つまづきポイント）
1. Firebaseの初期設定
1. 端末のトークン取得
1. 通知を送信する準備
1. 通知の送信

## サンプルコードのダウンロード
端末のトークンを取得する公式サンプルプログラムのダウンロード
```Bash:
git clone https://github.com/firebase/quickstart-js.git
```
通知を送信する公式サンプルプログラムのダウンロード
```Bash:
git clone https://github.com/firebase/quickstart-nodejs.git
```
## Node.jsのインストール
ここが一番のつまずきポイント。
サンプルプログラムが指定するバージョンを入れないとうまく動かない。
2025年3月8日現在のコードではnpmは9.X.X、nodeは18.0.0以上20.0.0以下にする必要がある。

Node.js公式サイトの[ダウンロードページ](https://nodejs.org/ja/download)から自身の環境に合ったコマンドを取得する。
Node.js v18.20.7はnpm 10.8.2がインストールされてしまうので注意。
npmのダウングレードが面倒な方は素直にv18.20.6以下をインストールするのが良い。

```Bash:Linux用のNode.js v18.20.6とnpmをnvmを使ってダウンロードする例
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
nvm install 18.20.6
nvm alias default 18.20.6
```
ターミナルを開き直すか```source ~/.bashrc```をしてからインストールされたバージョンを確認。
```Bash:npmとNode.jsのバージョン確認
npm -v   # npmのバージョンが表示される。
node -v   # Node.jsのバージョンが表示される。
```

<details><summary>Node.jsのバージョン指定方法</summary>

```nvm install```の際にマイナーバージョンを指定すればインストールされるnodeのバージョンを細かく指定できる。
```Bash:node v18.19.1をインストールする例
nvm install 18.19.1
```

メジャーバージョンのみ指定した場合、その時点での最新のマイナーバージョンが自動的に選ばれる。

```Bash:node v18の最新版をインストールする例
nvm install 18   # 2025年3月8日現在ではv18.20.7がインストールされる
```

```:インストール可能なNode.jsのバージョンの確認コマンド
nvm ls-remote
```
</details>

<details><summary>要求されるバージョンの確認方法</summary>

quickstart-js内のpackage.jsonファイルに以下の記述があるため、npmは9.0.0以上10.0.0未満、nodeは18.0.0以上20.0.0以下にしないといけないことがわかる。
```json:quickstart-js/package.json
"engines": {
  "npm": ">=9.0.0 <10.0.0",
  "node": ">=18.0.0 <=20.0.0"
},
```
quickstart-nodejsに関してはpackage.jsonファイル内にenginesの項目はないのでバージョンの指定はされていない。
但し最新版のNode.jsで動く保証がないのは過去のコードを利用する際によくあることである。
依存パッケージに関してはquickstart-nodejs/messaging/package.jsonに以下の記述があるため、```npm install```時にgoogle-auth-libraryは8.8.0、googleapisは118.0.0と互換性のあるバージョンがインストールされる。
```json:quickstart-nodejs/messaging/package.json
"dependencies": {
  "google-auth-library": "^8.8.0",
 "googleapis": "^118.0.0"
}
```
</details>

## Firebaseの初期設定
### Firebaseプロジェクトの作成
この辺の作業はググればたくさん出てくる。
quickstart-js/messaging/README.mdの1～3を参考に以下の作業を行う。

1. Googleアカウントで[Firebase console](https://console.firebase.google.com/)にログイン
1. プロジェクトを追加
1. 作成したプロジェクトにアプリを追加
1. [プロジェクト設定]→[Cloud messaging]タブを選び、[ウェブプッシュ証明書]欄の"Generate key pair"ボタンを押す

### サンプルファイルの編集
quickstart-js/messaging/README.mdの4を参考に、quickstart-js/messaging/config.tsにapiKeyやprojectIdをコピーする。
\<YOUR_PUBLIC_VAPID_KEY_HERE\>の部分は先ほど作ったウェブプッシュ証明書の鍵ペアとして表示されている文字列。

アプリ追加時の画面を閉じてしまった人は
1. [Firebase console](https://console.firebase.google.com/)で作成したプロジェクトを選択
1. 左のアイコン群から[歯車アイコン]→[プロジェクトの設定]を開く
1. [マイアプリ]欄の[次に Firebase を初期化し、使用するプロダクトの SDK の利用を開始します。]の下に必要な情報が表示される

### Firebase CLIのインストールと設定
quickstart-js/messaging/README.mdの5,6を参考に、quickstart-js/messagingディレクトリにて以下を実行する。
```bash:
# Firebase CLIのインストール
npm install -g firebase-tools

# Firebaseへログイン
firebase login

# Firebaseプロジェクトの指定
firebase use --add
```

### 依存パッケージのインストールとデプロイ
quickstart-js/messaging/README.mdのRunning the app using the Firebase CLI下の1,2およびTo deploy the sample app to production下の項目を参考に以下の作業を行う。

```Bash:quickstart-js/messagingディレクトリで以下のコマンドを実行
# 依存パッケージのインストール
npm install

# ビルド
npm run build

# デプロイ
firebase deploy
```

## 端末のトークン取得

Webページ（https://\<project_id\>.firebaseapp.com）にアクセスして[REQUEST PERMISSION]ボタンを押すと、トークンが文字列として表示されるのでメモする。

一度トークンを取得すれば次回以降はwebページへアクセスするだけでトークン文字列が表示される。

AndroidやWindowsではこの手順で問題ないが、iOSの場合はサイトをホーム画面に追加しないと通知が受け取れない。
Safariの場合は[appleのサポートページ](
https://support.apple.com/ja-jp/guide/iphone/iph42ab2f3a7/17.0/ios/17.0#iph4f9a47bbc)を参考にホーム画面へ追加し、追加したアイコンを開いて[REQUEST PERMISSION]ボタンを押す。
Chromeについても同じ[四角に上矢印ボタン]からホーム画面へ追加できる。

## 通知を送信する準備

### プロジェクトにサービスアカウントの追加
quickstart-nodejs/messaging/README.mdを参考にサービスアカウントを追加する。
1. [Firebase console](https://console.firebase.google.com/)で作成したプロジェクトを選択
1. 左のアイコン群から[歯車アイコン]→[プロジェクトの設定]を開く
1. [サービスアカウント]のタブを選ぶ
1. [新しい秘密鍵の生成]をクリックして[キーを生成]を選ぶ
1. jsonファイルのダウンロードが始まるのでservice-account.jsonとしてquickstart-nodejs/placeholdersディレクトリに保存する

サービスアカウントの詳細については公式ドキュメントを参照。
https://firebase.google.com/docs/admin/setup?hl=ja#initialize_the_sdk_in_non-google_environments

### 依存パッケージのインストール
```Bash:quickstart-nodejs/messagingディレクトリで以下のコマンドを実行
npm install googleapis
```
### サンプルプログラムの編集
quickstart-nodejs/messaging/index.jsを開く。
+ 12行目の\<YOUR-PROJECT-ID\>を自身のプロジェクトIDに書きかえる
+ 113~123行目のbuildCommonMessage関数において116行目の"topic:"項目を削除する
+ 削除した"topic:"項目の代わりに"token:"項目として取得したトークン文字列を書く

設定できるフィールドについては公式ドキュメントを参照。
https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?hl=ja

## 通知の送信

quickstart-nodejs/messagingディレクトリで以下のコマンドを実行する。
```Bash:
node index.js common-message
```
正しく設定できていればターミナルに以下のような送った通知の内容とレスポンス（message id）が表示される。
```Bash:ターミナルの実行例
FCM request body for message:
{
  "message": {
    "token": "<TOKEN>",
    "notification": {
      "title": "FCM Notification",
      "body": "Notification from FCM"
    }
  }
}
Message sent to Firebase for delivery, response:
{
  "name": "projects/<PROJECT-ID>/messages/<MESSAGE ID>"
}
```

## サンプルプログラムだと通知が2個送られてくる問題への対応
通知を送れるようにはなったが、なぜが2通届くという謎現象に悩まされた。
<img width=400, alt=20250308_171252.png, src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4015415/282e5579-34b2-42ff-a614-1958e32dbd34.png>

1通は自分が書き換えた通知だが、もう1通は
タイトル：Background Message Title
本文：Background Message body.
と全く無意味な通知内容。

調べた結果、トークンを取得する方のサンプルプログラムに2通目の謎通知を発出する部分を見つけた。
具体的にはquickstart-js/messaging/firebase-messaging-sw.jsの47〜58行目、messaging.onBackgroundMessage関数にて追加の通知登録が行われていた。
```js:quickstart-js/messaging/firebase-messaging-sw.js:Line47~58
messaging.onBackgroundMessage(function(payload) {
  console.log('[firebase-messaging-sw.js] Received background message ', payload);
  // Customize notification here
  const notificationTitle = 'Background Message Title';
  const notificationOptions = {
    body: 'Background Message body.',
    icon: '/firebase-logo.png'
  };

  self.registration.showNotification(notificationTitle,
    notificationOptions);
});
```
この関数のself.registration.showNotificationの部分（56,57行目）をコメントアウトしたところ無事に送りたい通知のみになった。

反映させるには、コメントアウト後にビルドとデプロイをし直して
```bash:quickstart-js/messagingディレクトリで以下のコマンドを実行
npm run build   # ビルド
firebase deploy   # デプロイ
```
Webページ（https://\<project_id\>.firebaseapp.com）にアクセスする。
表示されたトークン文字列は変わっていなくても問題ない。

## 参考情報
Firebase Cloud Messagingの公式ドキュメント
https://firebase.google.com/docs/cloud-messaging?hl=ja
APIの移行情報
https://firebase.google.com/docs/cloud-messaging/migrate-v1?hl=ja