FCM(Firebase Cloud Messaging)でWeb Pushする方法

# はじめに
FCM（Firebase Cloud Messaging）でWeb Pushする記事はいくつかありますが、2023年6月20日に非推奨になっているCloud Messaging API（レガシー）を使用した記事が多いので、最新のFirebase Cloud Messaging API（V1）で動かしてみます。

・FCMとは
　Googleが提供するクラウドベースのメッセージングソリューションです。主にモバイルおよびWebアプリケーション向けに設計されており、リアルタイムでメッセージを送受信するためのサービスです。
・Web Pushとは
　Webブラウザを介してユーザーに対してリアルタイムで通知を送信する技術です。主にWebサイトやWebアプリケーションで利用され、ユーザーがサービスにアクセスしていないときでも、重要な情報を届けることができます。

# こんな人に読んでほしい
・Web Push導入を検討している方

# Firebase登録
まず初めに、下記記事を参考にして、プロジェクトとマイアプリをFirebaseに登録します。
https://ralacode.com/blog/post/register-firebase/

登録後、下記情報をコピーしておきます。
・「プロジェクトの設定」-「全般」-「マイアプリ」のプロジェクトIDとCDNのコード
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3659381/3b951a28-0dd9-0427-7d21-7343724c2c3a.png)

・「プロジェクトの設定」-「Cloud Messaging」の鍵ペア
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3659381/23cd437d-593b-5c4e-8136-a91a9635b375.png)

・「プロジェクトの設定」-「サービスアカウント」の新しい秘密鍵を生成(jsonファイルのダウンロード)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3659381/1052a390-013c-9c68-efb0-b2ceddd5215d.png)


# ソースコード作成
次にソースコードを作成していきます。
ファイル構成は以下の通りです。
```
.
├── front
│   ├── index.html
│   ├── firebase-messaging-sw.js
│   ├── manifest.json
│   └── service-worker.js
└── server
    ├── push.php
    └── XXXXXXXXXX.json
```

# index.html
Web Pushを受信するWebアプリで、「トークン取得」ボタン押下時にFCMトークンを取得し、ブラウザに表示します。
本来であればFCMトークンはサーバーで適切に管理する必要がありますが、今回は検証目的なので簡易的に実装しています。

~~~php:index.html
<!DOCTYPE html>
<html>
<head>
  <meta charset='utf-8/'>
  <link rel="manifest" href="./manifest.json">
  <script>
    // service workerが有効なら、service-worker.js を登録します
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.register('./service-worker.js');
   }
  </script>
</head>
<body>
    <button id="allow_push_notification">トークン取得</button>
    <p id="output"></p>
    <script type="module">
        import { initializeApp } from "https://www.gstatic.com/firebasejs/10.12.2/firebase-app.js";
        import { getMessaging, getToken } from "https://www.gstatic.com/firebasejs/10.12.2/firebase-messaging.js";

        // Your web app's Firebase configuration
        const firebaseConfig = {
            apiKey: "XXXXXXXXXX",
            authDomain: "XXXXXXXXXX",
            projectId: "XXXXXXXXXX",
            storageBucket: "XXXXXXXXXX",
            messagingSenderId: "XXXXXXXXXX",
            appId: "XXXXXXXXXX"
        };

        // Initialize Firebase
        const app = initializeApp(firebaseConfig);

        // 通知を許可するボタンが押された時のアクション
        const btn = document.getElementById('allow_push_notification');
        btn.addEventListener("click", function () {
            //let promise = Notification.requestPermission();
            //console.log("promise: ",promise);
            if (!("Notification" in window)) {
                // ブラウザーが通知に対応しているか調べる
                alert("このブラウザーはデスクトップ通知には対応していません。");
            } else if (Notification.permission !== "denied") {

            //if(Notification.permission !== "denied") {
                Notification.requestPermission().then((permission) => {
                    const messaging = getMessaging();
                    // Add the public key generated from the console here.
                    getToken(messaging, {vapidKey: "★鍵ペア★"})
                        .then((token) => {
                            if (token) {
                                // tokenを出力
                                //document.getElementById('output').textContent = `${token.Promise}`;
                                console.log( "token", token);
                                document.getElementById('output').textContent = token;
                            }else{
                                console.log('登録トークンがありません。生成する許可をリクエストします。');
                            }
                        })
                        .catch((err) => {
                            console.log('getToken Error',err);
                        });
                });
            }
        });
    </script>
</body>
</html>
~~~

:::note info
「"XXXXXXXXXX"」の部分は、FirebaseのCDNのコードに変更してください。
「★鍵ペア★」の部分は、Firebaseの鍵ペアに変更してください。
:::

# firebase-messaging-sw.js
FCMの[Service Worker](https://zenn.dev/s_takashi/articles/b01980461f3b21)であり、Web Push通知を受信した際に動作します。
~~~php:firebase-messaging-sw.js
// Push通知を受け取ると呼ばれる
self.addEventListener('push', function (event) {
    // メッセージを表示する
    var data = {};
    if (event.data) {
      data = event.data.json();
    }
    var title = data.notification.title;
    var message = data.notification.body;
    event.waitUntil(
      self.registration.showNotification(title, {
        'body': message
      })
    );
});
~~~

# manifest.json / service-worker.js
iOSでWeb Pushを受信する場合は[PWA化](https://www.pushcode.jp/blog/webpush-notification-ios-pwa)が必要です。
PWA化する場合は下記２つのファイルを作成します。
(iOSで使用しない場合は不要です)
~~~php:manifest.json
{
    "$schema": "https://json.schemastore.org/web-manifest-combined.json",
    "name": "Web Push Test",
    "short_name": "Push Test",
    "start_url": ".",
    "display": "standalone",
    "background_color": "#fff",
    "description": "Web Push テスト"
}
~~~

~~~php:service-worker.js
var CACHE_NAME = 'pwasample';
var urlsToCache = [
    '/apps/pwa/',
    '/apps/pwa/index.html'
];

self.addEventListener('install', function(event) {
    event.waitUntil(
        caches
            .open(CACHE_NAME)
            .then(function(cache) {
                return cache.addAll(urlsToCache);
            })
    );
});

self.addEventListener('fetch', function(event) {
    event.respondWith(
        caches
            .match(event.request)
            .then(function(response) {
                return response ? response : fetch(event.request);
            })
    );
});
~~~

# Webアプリの実行
Service Workerを実行するためにはHTTPS化が必要です。
とりあえず動かしたいのであれば、下記記事を参考にしてホスティングしてください。
[爆速で静的WEBをHTTPSホスティングする方法](https://qiita.com/nanin/items/a2436dbbcd14628f8c81)

ブラウザ表示後、「トークン取得」ボタンを押すとFCMトークンが表示されます。
これをコピーしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3659381/beed4c8c-df65-3965-8c1c-76c7b711356c.png)

# push.php
ここからはサーバー側の実装になります。
コマンドラインでさくっと実行したいのでphpで実装してみます。
~~~php:push.php
<?php
require 'vendor/autoload.php';

//アクセストークン取得
$client = new Google_Client();
$client->setAuthConfig('XXXXXXXXXX.json');
$client->addScope('https://www.googleapis.com/auth/firebase.messaging');
$client->refreshTokenWithAssertion();
$access_token = $client->getAccessToken();

// 送るメッセージ　tokenに登録トークンを設定
$json = '{
    "message":{
        "notification":{"title": "ここにタイトルを入力",
        "body": "ここにボディを入力"},
        "token":"★FCNトークン★"}
}';

// curlの初期化
$ch2 = curl_init(); 

// アクセストークンをhttpヘッダーに設定
$headers = array(
    'Content-Type: application/json',
    'Authorization: Bearer ' . $access_token["access_token"]
);

curl_setopt_array($ch2, array(
    CURLOPT_URL => 'https://fcm.googleapis.com/v1/projects/★FCNプロジェクトID★/messages:send',
    CURLOPT_HTTPHEADER => $headers,
    CURLOPT_SSL_VERIFYPEER => false,
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POSTFIELDS => $json
));

// push送信
$response = curl_exec($ch2);

curl_close($ch2);
~~~

:::note info
「XXXXXXXXXX.json」は秘密鍵のjsonファイル名に変更してください。
またpush.phpと同じフォルダに秘密鍵のjsonファイルを置いてください。
「★FCNトークン★」はWebアプリに表示されているFCMトークンに変更してください。
「★FCNプロジェクトID★」はFCMのプロジェクトIDに変更してください。
:::

# Google API Clientライブラリのインストール
下記コマンドを実行し、ライブラリをインストールします。
~~~
composer require google/apiclient:^2.15.0
~~~

# サーバーからPUSH送信
~~~
php push.php
~~~

# 実行結果
代表的なOS/ブラウザでのWeb Push実行結果を添付します。
|OS|表示結果|
|---|---|
|Windows11|Chrome<br><img width=400px src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3659381/457dbdf5-b44a-8a3b-3696-4f43f5a75ff5.png"><br>Edge<br><img width=400px src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3659381/43d7461d-2480-ccfd-c750-859c0e2c52fb.png"><br>FireFox<br><img width=400px src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3659381/89be3c5a-aca7-e8dc-b707-66a6fdef9563.png">|
|Mac|Chrome<br><img width=400px src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3659381/fb7d5ae2-2c5a-380b-8ea6-d0a7a93a5aee.png"><br>Safari<br><img width=400px src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3659381/8b2d9b9d-5d5d-8e93-06c8-95c9b2b20204.png">|
|Android13|Chrome<br><img width=400px src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3659381/d50c17c9-298e-cedc-6b9c-c3021fc73286.png">|
|iOS17.5.1|PWA<br><img width=400px src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3659381/3c6ae0f4-741a-8cac-cb99-a273be8cee52.png">|

# 最後に
今回はFCMでWeb Pushするための最低限のコードを書いてみました。
いろいろカスタマイズできるので、興味のある方は調べてみるとよいと思います。