1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Firebase Cloud MessagingによるWebプッシュ通知のメモ

Posted at

はじめに

最近、Firebase Cloud Messaging(以下FCM)によるWebプッシュ通知の機能開発を試みた。のだが、色々あってあまりうまい具合に動かず、結局OneSignalを使う形で舵を切りなおした。が、Firebase Cloud Messagingを使った機能開発で培った試行錯誤のノウハウをそのまま失ってしまうのは惜しいと考え、ここに備忘録という形で供養する。誰かのためになればいいなと思います。

必要なPermission

Google CloudのAPI Keyの画面で、API Keyに付与するPermissionを選択することになると思うが、FCMでデバイストークンを発行する・そのデバイストークン向けにWebプッシュ通知を送信するために必要なPermissionは以下4点。これ以外は必要ない。ただ、「少なくともこのPermissionがあれば最低限動いた」という意味であって、この中にも不要なものがあるかもしれない。

  • Firebase Management API
  • FCM Registration API
  • Firebase Cloud Messaging API
  • Firebase Installations API

デバイストークンの発行、管理、その他

  • PWA化は以下の記事が参考になりました。ありがとうございます。
    https://qiita.com/Coa3/items/c81c8c7984bd5fc4ec69
    • なお、Next.jsのWebアプリをiOS向けにPWA化するだけなら、manifest.jsonやアイコン類をつくって/public配下に格納しておくだけでよく、next-pwaのインストールは必要ない。これは公式ドキュメントでもそんなようなことが書かれている(公式ドキュメントではapp/manifest.tsを作るサンプルを提示しているが要するにmanifest.jsonつくって配置するのと同じなのでまあ好みの問題だと思う)
      https://nextjs.org/docs/app/building-your-application/configuring/progressive-web-apps
  • Webプッシュ通知に関しては以下の記事が参考になりました。ありがとうございます。
    https://qiita.com/nanin/items/f3382a871eb04bf6828e
    • ただし必要なのはfirebase-messaging-sw.jsのみで、service-worker.jsはなくてもデバイストークン発行可能だった。
  • デバイストークン発行するためにフロント側(Next.jsではClient-Components)でFirebaseのオブジェクトを生成することになるが、これにあたり指定が必要なフィールドはapiKeyprojectIdmessagingSenderIdappIdの4つだけで、他のプロパティ(authDomainstorageBucket)は指定不要だった。それとgetTokenの引数で渡すvapidKeyが必要。つまり、部分的には省くが以下の実装でtokenの変数にデバイストークンが発行されて格納される。
    const firebaseConfig = {
      apiKey: process.env['NEXT_PUBLIC_FIREBASE_API_KEY'],
      //authDomain: process.env['NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN'],
      projectId: process.env['NEXT_PUBLIC_FIREBASE_PROJECT_ID'],
      //storageBucket: process.env['NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET'],
      messagingSenderId: process.env['NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID'],
      appId: process.env['NEXT_PUBLIC_FIREBASE_APP_ID'],
    };
    if ('serviceWorker' in navigator) {
                            await navigator.serviceWorker.register('/firebase-messaging-sw.js');
    }
    ...
                        const app = initializeApp(firebaseConfig);
                        const messaging = getMessaging(app);
    ...
                        const token = await getToken(messaging, {
                                        vapidKey: process.env["NEXT_PUBLIC_FIREBASE_VAPID_KEY"] ,
                                        serviceWorkerRegistration: await navigator.serviceWorker.ready,
                                      });
    
  • 「FCMに登録されているデバイストークンの一覧」を取り出すためのAPIはないらしい。
    https://stackoverflow.com/questions/43060846/
    デバイストークンの保管は実装者の責任でやれということだそうだ。それは別にいいんだがFCMって際限なくデバイストークン発行可能ってわけでもないのではないのだろうか。調べられてない。
  • インストールしたPWAをアプリとして削除するとメッセージは届かなくなるがFCM SDKでの送信結果はsuccessになる。試してみた感じ、後述する送信データ形式不正の件も含め、「実際には届かないがSDKの実行結果は成功扱いになる」ケースはちょこちょこ起きる。多分FCM自身も最終的に「実機に届ける」部分までは把握しきれてないんじゃないか(SDKの範疇で実行に成功していればsuccessと返す仕様なんじゃないか)と予想する。個人の意見です。

FCMによるWebプッシュ通知

  • デバイス単体に送信するならhttps://fcm.googleapis.com/v1/projects/[プロジェクトID]/messages:sendcurlなりfetchなりすればよい。ここに書いてあります。→https://firebase.google.com/docs/reference/fcm/rest?hl=ja
    • ただ、上記の通り、これは「単体」にのみしか送信できない。昔の記事とか読むとtokensフィールドにstringの配列でデバイストークン並べれば送信できるような記述もチラホラ見かけるが、少なくとも試した限りではそうしたことはできなかった。後述するSDK使うと内部的にはどうやらAPI呼んでるっぽいのでAPIのリソースにも「複数端末への送信」に相当するものがありそうな気がするが、とりあえず公式には公開されていない。複数端末への送信も、forでグルグル回しながらその分このAPIコールすれば出来なくはない、が…
  • 単発で複数端末に送信するならfirebase-admin/messagingインストールしてmessaging.sendEachForMulticastを使う。ただしこれも最大500人のデバイストークンが対象となる。501以上渡すどうなるのかはわからない。こういう場合は500で配列区切ってループをネストするしかない、のかな?なお、昔の記事だとsendAllとかsendMulticastとか使う例が出てくるがこれは2024年12月現在どうやら廃止されている→https://firebase.google.com/docs/reference/admin/node/firebase-admin.messaging.messaging.md
  • メッセージのペイロードにあたるBaseMessageクラスは全部のフィールドが必須ではないからか、意味不明なオブジェクト渡してもSDKでの実行自体はエラーなく動いてしまう(「送信できた」としてレスポンスが返ってくる)。例えば以下のオブジェクトは実際には端末にはWebプッシュ通知としては届かないが、レスポンスは"success"で返ってくる:
    const admin = require('firebase-admin');
    const {initializeApp, } = require('firebase-admin/app');
    const registrationTokens = [
        'eLKZxsvF3jkwGqVn691234:AP...',
    ];
    (async()=>{
      try {
            const app = initializeApp({
              credential: admin.credential.cert({
                  projectId: process.env['FIREBASE_PROJECT_ID'],
                  clientEmail: process.env['FIREBASE_CLIENT_EMAIL'],
                  privateKey: process.env['FIREBASE_PRIVATE_KEY'].replace(/\\n/g, '\n'),
              }),
          });
          const messaging = getMessaging(app);
    
          // notificationとdataオブジェクトはbodyで囲う必要がない(そんな仕様はない)のでこれは送信データとしては形式不正
          const messageForMultipleDevices = {
              body: {
                  notification: {
                      title: 'test title by sendMulticast',
                      body: 'test body by sendMulticast:' + new Date().toUTCString(),
                  },
                  data: {
                      test: 'data.test by sendMulticast',
                  },
              },
              tokens: registrationTokens,
          };
          const r = await messaging.sendEachForMulticast(messageForMultipleDevices);
          console.log(`result:`);
          console.log(JSON.stringify(r));
          
      } catch(error) {
          throw error;
      }
    })();
    
    これを実行すると
    result:
    {"responses":[{"success":true,"messageId":"projects/[project id]/messages/[uuid]"}],"successCount":1,"failureCount":0}
    
    となって、正常に送信できたようなレスポンスを返してくるが、データ形式が不正なので(端末に届けるべき送信メッセージのペイロードが存在しないので)当然実機には何も届かない。ちなみにデータ形式としては以下が正解。(これで実機にWebプッシュ通知のメッセージが届いた実績があります。)
            const messageForMultipleDevices = {
              notification: {
                  title: 'test title by sendMulticast',
                  body: 'test body by sendMulticast:' + new Date().toUTCString(),
              },
              data: {
                  test: 'data.test by sendMulticast',
              },
              tokens: registrationTokens,
          };
    
    • ドキュメントには特に明記されていないが、実際に何度か試してみたところでは、上記のように送信データに不正なオブジェクトを渡して送信すると(実際には届かないがSDKのレスポンスはsuccessになるような実行をすると)、どうもそのときに送信先に指定したデバイストークンがFCMの中でしれっと「無効」な扱いにされてる(以後、そのデバイストークンには正当なメッセージオブジェクトを渡したとしても通知が届かなくなる)気がする。実際、そのあとデバイストークン再発行したら別の値になった。ここは実体験によるものであり、仕様を言及するものではないですが、気になったので書いておきます。
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?