iOSでFCMを使ってWebPushを受信する

ついにiOSでFCMからのWebPushに対応したため、実装時に使ったコードをシェアしておきます。
PHP Laravel、jQueryを使ってます。

# WebPushを受信するための条件
1. iOS16.4以上
2. Safari（Chromeでは下記のPWA登録ができないようなので今はSafariのみ）
3. PWAとしてホーム画面に登録
4. 通知の許可をするボタンを作成しておき、ユーザーに押して貰う必要がある

1については、iOS16.4にアップグレードできない古い機種は諦めてもらうしかないようです。
2は、できたらChromeにも対応して欲しいですね。
3のホーム画面に登録の手順は https://marble-heroes.com/blog/ubbadumj0w などを参考にしてもらえればと思います。私自身はアップル製品はほぼ使わないので少し戸惑いました。

# コード
上記の通り、ボタンを用意してユーザーに通知を許可してもらわないとならないため、HTML側はこんな感じでボタンを用意しておきます。（アクションさえ起こしてもらえれば別にボタンでなくても何でもいいですが）

```HTML
<button id="allow_push_notification">通知を許可する</button>
```

続いてJavascript部分です。
コード内のvapidKeyはちょっと分かりにくいですがFCM管理画面内で生成できます。このあたり[stack overflow](https://stackoverflow.com/questions/54996206/firebase-cloud-messaging-where-to-find-public-vapid-key) あたりを参考にしてもらえれば。

```javascript
<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-messaging.js"></script>

<script>
// Firebase configuration
const firebaseConfig = {
    apiKey: "{{ env('FIREBASE_API_KEY') }}",
    authDomain: "{{ env('FIREBASE_AUTH_DOMAIN') }}",
    projectId: "{{ env('FIREBASE_PROJECT_ID') }}",
    storageBucket: "{{ env('FIREBASE_STORAGE_BUCKET') }}",
    messagingSenderId: "{{ env('FIREBASE_MESSAGING_SENDER_ID') }}",
    appId: "{{ env('FIREBASE_APP_ID') }}",
    measurementId: "{{ env('FIREBASE_MEASUREMENT_ID') }}"
};

$(document).ready(function () {
	// Check if the browser supports notifications
	if(("Notification" in window))
	{
		// 通知が許可されていない場合は通知を許可するボタンを表示する
        // 既に許可されている場合はFCMトークンを取得してDBに保存する
		if(Notification.permission !== "granted" && Notification.permission !== "denied")
		{
			$('#allow_push_notification').show();
		}
        else
        {
            get_token();
        }
	}
});

// Initialize Firebase
firebase.initializeApp(firebaseConfig);

const messaging = firebase.messaging();

// 画面がフォアグラウンドの場合にここで受信
messaging.onMessage((payload) => {
    let title = payload.notification.title;
    let body = payload.notification.body;

    console.logtitle);
    console.log(body);
});

// 通知を許可するボタンが押された時のアクション
$('#allow_push_notification').click(function () {
    if(Notification.permission !== "denied")
    {
        Notification.requestPermission().then((permission) => {
            get_token();

            // 通知が許可されたのでここで通知許可ボタンを隠したりする
        });
    }
});

// FCMトークン取得
function get_token()
{
    messaging.getToken({ vapidKey: "{{ env('FIREBASE_VAPID_KEY') }}",}).then((currentToken) => {
        if(currentToken)
        {
            console.log('FCM TOKEN: '+currentToken)

            // ここで取得したFCMトークンをDBに保存
        }
        else
        {
            console.log("No registration token available.");
        }
    });
}
</script>
```

ちなみに、PCのChromeなどiOS以外の場合は以下のような感じでNotification.requestPermission()でラップします。iOSの場合はなぜかこれだと動作しなかったので上記のように外に出してます。正しいやり方ではないかもしれないのでもし分かる方がいたら間違ってるよと教えてもらえると助かります。

```javascript
Notification.requestPermission().then((permission) => {
    if(permission === 'granted')
    {
        get_token();

        messaging.onMessage((payload) => {...
    }
}
```

次にウィンドウがバックグラウンドにあるときや閉じられているときに通知を受信したときのためにfirebase-messaging-sw.jsを作成してサイトのルートに置きます。
パラメータは適当に変更してください。

```firebase-messaging-sw.js
importScripts("https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js");
importScripts("https://www.gstatic.com/firebasejs/8.10.0/firebase-messaging.js");

firebase.initializeApp({
    apiKey: "",
    authDomain: "",
    projectId: "",
    storageBucket: "",
    messagingSenderId: "",
    appId: "",
    measurementId: ""
});

const messaging = firebase.messaging();

messaging.onBackgroundMessage((payload) => {
    console.log(
        "[firebase-messaging-sw.js] Received background message ",
        payload
    );
    const data = JSON.parse(payload.data.value);
    const notificationTitle = `${data.title} | ${data.body} | ${data.sender}`;
    const notificationOptions = {
        body: data.text,
        icon: "/img/icon.png",
    };

    self.registration.showNotification(notificationTitle, notificationOptions);
});

```

最後にmanifest.jsonは以下のような感じで作成してサイトのルートに置きます。
こちらもパラメータは適当に変更してください。gcm_sender_idはこの数字のままで固定らしいです。

```manifest.json
{
    "name": "app name",
    "short_name": "app name",
    "icons": [
        {
            "src": "/img/icon.png",
            "sizes": "180x180",
            "type": "image/png"
        }
    ],
    "start_url": "/",
    "display": "standalone",
    "theme_color": "#e91e63",
    "gcm_sender_id": "103953800507"
}
```

以上です。
Chromeで動作していたそのままのコードで動くとは思っていませんでしたが案の定あれこれ試行錯誤する羽目になりました。
ユーザー側も色々と条件を整えたり下準備が必要なので一般に広まるかというとどうかなと思いますが、クローズドなサービスなどで使用するユーザーが限定されている場合などでは使い途もあるかと思います。