FCMバックグラウンド通知クリック時の挙動をカスタマイズする～firebase-messaging-sw.jsの深掘り～

## はじめに
本記事ではFCM (Firebase Cloud Messaging) と ServiceWorker を利用して、ウェブアプリケーションにおけるバックグラウンドプッシュ通知の実装について解説します。特に、Firebase SDK の標準の通知クリック時の挙動が特定の要件に合致しなかったため、筆者が試行錯誤の末に行ったカスタマイズ方法を共有します。

なおSDKを使った実装方法については、以前の記事にて紹介しているので、[こちら](https://qiita.com/toshiki_iseki/items/b90b7fe12f6aa63a7e2f)をご覧ください


## firebase-messaging-sw.jsについて

### firebase-messaging-sw.js の役割
`firebase-messaging-sw.js`ファイルは、ウェブアプリケーションがフォアグラウンドにない場合や閉じられている場合にプッシュ通知を処理するService Workerスクリプトです。
ブラウザはバックグラウンドでこのスクリプトを実行し、FCMからメッセージを受信すると、通知の表示やその他のバックグラウンドタスクを実行します。
このService Workerはメインのウェブアプリケーションとは独立して動作するため、アプリケーションが開かれていなくても通知機能を使うことができます。

### プッシュ通知クリック時の遷移URL
`firebase-messaging-sw.js`は、通知クリック時に特定のURLに遷移させることができます。
遷移させるURLの指定は`webpush.fcm_options.link` プロパティで行います。
以下はcurlコマンドでFCMに通知内容を送信するときの例です。
```
curl -X POST \
  -H "Authorization: Bearer <認証トークン>" \
  -H "Content-Type: application/json" \
  -d '{
    "message": {
      "token": "デバイストークン",
      "notification": {
        "title": "通知テスト",
        "body": "これは通知メッセージです"
      },
      "webpush": {
        "fcm_options": {
          "link": "任意の遷移先"
        }
      }
    }
  }' \
  https://fcm.googleapis.com/v1/projects/<認証情報を取得したFirebaseのプロジェクト名>/messages:send

```

この内容で通知がデバイスに表示された場合、以下のような順序で遷移動作します。

1. 同一オリジンURLのタブのフォーカス: ブラウザで既に開いているタブの中に、webpush.fcm_options.link で指定されたURLと同一オリジンのURLタブが存在する場合、そのタブがフォアグラウンドに表示されます。

2. 新しいタブで開く: 上記の条件に合致するタブが存在しない場合、fcm_options.link で指定されたURLが新しいタブで開かれます。

標準仕様では、例えば、すでに同一オリジンでページ1（例: `https://example.com/page1`）を開いている場合に、通知のクリックでページ2（例: `https://example.com/page2`）へ遷移させたい、といったケースに対応できません。

この時の解決法としては、`firebase-messaging-sw.js`を一切使うことなく、自前で通知表示から遷移処理まで実装してしまうことがまず候補として挙がってきますが、それはやりたくないということでカスタマイズの方法を考えました。

## カスタマイズ方法

結論から言うと、`firebase-messaging-sw.js`をServiceWorker内でインポートする前に、先にクリックイベントのロジックをSDKの処理を上書きできるように記載してしまうことです。
これはかなり強引な方法だと思いますので、推奨はできませんし、もしこの方法を採用する場合は、SDKの他の処理への影響や、動作確認を念入りにされることをお勧めします。

実際のServiceWorker内のコードは以下の通りです。
できるだけSDKとの競合を避けるためwebpush.fcm_options.linkにリンクを入れるのではなく、データメッセージ内に遷移したいURLを入れることもいいかと思います。


```javascript
// Firebase SDKより先にクリックイベントを定義
self.addEventListener('notificationclick', (event) => {
  console.log('カスタム notificationclick: イベント受信。', event);

  // 通知を閉じる
  event.notification.close();

  let linkUrl = null;
  // リンクURLを fcmOptions.link からのみ取得する
  if (event.notification.data) {
    console.log('カスタム notificationclick: 受信データ:', JSON.stringify(event.notification.data));

    // 遷移先リンクの取得
    const fcmData = event.notification.data.FCM_MSG;
    linkUrl = fcmData.notification.click_action;

  }

  // リンクが見つからない場合のフォールバックURL
  const urlToOpen = linkUrl || 'https:///site?siteId='; // 指定がなければルートを開く
  console.log(`カスタム notificationclick: URLを開こうとしています: ${urlToOpen}`);

  // clients.openWindow() を使用して常に新しいタブ/ウィンドウで開く
  // 既存クライアントの検索・フォーカス処理は行わない
  const transitionURL = clients.openWindow(urlToOpen);

  event.waitUntil(transitionURL);
});

// Firebase SDK のモジュールを後からインポート
import { initializeApp } from "https://www.gstatic.com/firebasejs/11.2.0/firebase-app.js";
import { getMessaging, onBackgroundMessage } from "https://www.gstatic.com/firebasejs/11.2.0/firebase-messaging-sw.js";

// Firebase 設定
const firebaseConfig = {
  apiKey: "",
  authDomain: "",
  projectId: "",
  storageBucket: "",
  messagingSenderId: "",
  appId: ""
};

// Firebase アプリを初期化
const app = initializeApp(firebaseConfig);

// Messaging インスタンスを取得
const messaging = getMessaging(app);

// バックグラウンドでのメッセージ受信ハンドラ (ログ出力のみ)
onBackgroundMessage(messaging, (payload) => {
  console.log('[firebase-messaging-sw.js] Received background message: ', payload);

});


```

## 終わりに

以上ができるだけ`firebase-messaging-sw.js`を使いつつ、一部仕様をカスタマイズする方法でした。
なお本来の使い方ではないので、できるだけこの方法は使わないことがベストではありますが、クリックイベントだけではなく、ほかにもこの方法でカスタマイズできることがあると思いますので、試していただければ幸いです。