🔔

FCMのユーザー購読トピック一覧を取得する方法

2024/10/10に公開

Firebase Cloud MessagingはFirebaseが提供するプッシュ通知を打つサービスです。ユーザーがtopicを購読することで、通知をカテゴリに分けて打ち分けるようなことができます。

Flutter大学では、以下のUIのような通知設定を用意し、ユーザーが受け取りたい通知と受け取りたくない通知を設定できるようにしています。

Firebase Messaging SDKにて通知のtopicを登録と削除はできるのですが、購読状況を取得するAPIが公式で提供されていません。

実はGCP系のInstance ID APIで取得することができるのですが、deprecatedとなっています。

https://developers.google.com/instance-id/reference/server

ちなみに、⇧のdocの一番上に、 Firebase installations APIに移行してくださいと書いてあるのですが、そこではtopic購読状況取得方法についての言及がありません。なので色々記事を見ましたが、購読状況をshared_preferenceを使うなりFirestoreに入れるなりして別管理するしかないというのがネットでの総意見となっています。

本記事では、一旦2024年10月時点で、topic購読状況一覧を取得するためにdeprecatedだけどInstance ID APIを使っちゃう方法を記したいと思います。ただし、API自体はdeprecatedです。

前提条件

以下は出来ているという前提で進めます。

  • Firebase functionsのセットアップは完了している
  • functionsにおける基本的なTypeScriptとライブラリのインストール方法を知っている
  • Flutterからfunctionsを呼び出す方法を知っている
  • 基本的なFlutterのUIの実装方法を知っている

Firebase functionsでtopic一覧取得APIを用意する

以下のようにfunctionsの方でアプリから呼び出せるような関数を用意します。アプリの方からiidTokenを受け取って、それを使ってInstance ID APIを叩きます。

index.ts
import * as functions from 'firebase-functions';
import axios from 'axios';

export const getNotificationTopics = functions.region('asia-northeast1').https.onCall(async (data, context) => {
    const iidToken = data.iidToken;
    const topics = await _getUserNotificationDetail(iidToken); // 以下に別関数で記す
    return topics;
});

以下に、別関数に切り出しました。_getAccessTokenをまた後述しますが、サービスアカウントからauthTokenを作ってもらい、それで認証します。

https://iid.googleapis.com/iid/info/${iidToken}?details=true が冒頭で述べたようにdeprecatedですが、一応2024年10月現在、この認証方法をとれば動きます。

index.ts
async function _getUserNotificationDetail(iidToken: string): Promise<string[]> {
    const authToken = await _getAccessToken();

    const config = {
        headers: {
            Authorization: `Bearer ${authToken}`,
            access_token_auth: 'true',
        },
    };
    const response = await axios.get(`https://iid.googleapis.com/iid/info/${iidToken}?details=true`, config);
    const json = response.data;
    const topics = json?.rel?.topics;

    if (!topics) {
        return [];
    }
    const keyList = Object.keys(topics);
    return keyList;
}

ちなみに、jsonは以下のような形式で返ってきます。platformがiOSなのはiOSからiidTokenを受け取っているからです。このrel.topicsの中に購読topic一覧が入ってるので、入ってるkeyだけの配列をアウトプットとして上記の関数では返しています。

{
	"applicationVersion": "2.5.6",
	"application": "jp.kboy.kboyflutteruniv.dev",
	"authorizedEntity": "386988152963",
	"rel": {
		"topics": {
			"all": {
				"addDate": "2024-10-08"
			},
			"github": {
				"addDate": "2024-10-08"
			},
			"member": {
				"addDate": "2024-10-08"
			},
			"project": {
				"addDate": "2024-10-08"
			},
			"video": {
				"addDate": "2024-10-08"
			}
		}
	},
	"platform": "IOS"
}

以下がaccesTokenの作成部分です。もしfunctionsをすでに実装しているなら、adminSDKを使うためにサービスアカウントを作っているはずなので、その情報を使ってaccess_tokenをゲットしてください。

index.ts
const { JWT } = require('google-auth-library');

function _getAccessToken(): Promise<string> {
    // サービスアカウントを環境変数から取得
    const serviceAccountConfig = functions.config().secret.service_account;

    return new Promise(function (resolve, reject) {
        const jwtClient = new JWT(
            serviceAccountConfig.client_email,
            null,
            serviceAccountConfig.private_key.replace(/\\n/g, '\n'),
            ['https://www.googleapis.com/auth/firebase.messaging'], // scopes
            null
        );
        jwtClient.authorize(function (err: any, tokens: { access_token: string | PromiseLike<string> }) {
            if (err) {
                reject(err);
                return;
            }
            resolve(tokens.access_token);
        });
    });
}

iOS/Androidアプリ側から呼び出す

あとは、モバイルアプリ側から先ほどのfunctionを呼び出し、配列で受け取った購読topic状況をUIにいい感じに反映してください。

      // アプリのインスタンス情報を取得
      // https://developers.google.com/instance-id/reference/server#get_information_about_app_instances
      String? iidToken = await FirebaseMessaging.instance.getToken();

      final functions = FirebaseFunctions.instanceFor(
        app: Firebase.app(),
        region: 'asia-northeast1',
      );
      final callable = functions.httpsCallable('api-getNotificationTopics');
      final result = await callable.call(
        {
          'iidToken': iidToken,
        },
      );
      final topics = result.data.cast<String>();

ちなみにFlutter大学アプリでは、UIは以下みたいにしてます。

以上です!

参考にした記事

https://developers.google.com/instance-id/reference/server

https://firebase.google.com/docs/cloud-messaging/migrate-v1?hl=ja

https://github.com/googleapis/google-auth-library-nodejs/blob/d8c70b9d858e1ef07cb8ef2b5d5d560ac2b2600a/README.md#json-web-tokens

まとめ

ただし、最初に述べたようにInstance ID APIはdeprecatedなので、いつか使えなくなるかもしれません。(2024年10月現在は動く)

この機能を実装しているのは

Flutter大学のiOS/Androidアプリです。私がたまにアプリをアップデートしております。

https://flutteruniv.com/

Flutter大学に入るとこのプロジェクトのGitHubも自由に見ることができます。もちろんプルリクも歓迎です。

Flutter大学

Discussion