みかづきブログ・カスタム

基本的にはちょちょいのほいです。

Firebase Cloud Messaging + Cloud Functionsを使って、iOS16.4のPWA(Progressive web apps)にプッシュ通知を送る ✉️

前回は、Navigator.setAppBadgeを使ってアイコンにバッジをつけました

blog.kimizuka.org

今回はトークンをデータベースに登録して、サーバからプッシュ通知を送信してみます。

Safariでプッシュ通知を受け取る条件

httpsでWebサイトをつくる
❶のウェブサイトをホーム画面に追加する(displayをstandaloneにする)
ユーザーアクションきっかけで通知の許可をとる

が、必要です。

事前準備

❶ Firebaseのプロジェクトを準備する

  • Hosting、Messaging、Functions、Cloud Firestoreを有効にします

❷ Firebaseのプランを切り替える

  • Sparkプラン(無料)だと、Functionsが利用できないため、Blazeプラン(従量課金制)に切り替えます

❸ ウェブアプリの追加

  • プロジェクトの設定 > 全般 > マイアプリ > アプリの追加 からWebAppを追加します

❹ Firebase Cloud Messaging API(V1)を有効にして、ウェブプッシュ証明書を発行する

  • プロジェクトの設定 > Cloud Messaging > ウェブの構成 > ウェブプッシュ証明書 の鍵ペアをメモしておきます


ソースコード

public/index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>pwa notification</title>
    <link rel="manifest" href="/manifest.json" /><!--manifest.jsonを読み込む-->
  </head>
  <body>
    <button class="push">push</button>
    <textarea id="token"></textarea>
    <script type="module">
      import { initializeApp } from 'https://www.gstatic.com/firebasejs/9.19.0/firebase-app.js';
      import { getMessaging, getToken, onMessage } from 'https://www.gstatic.com/firebasejs/9.19.0/firebase-messaging.js';
      import { doc, getFirestore, setDoc } from 'https://www.gstatic.com/firebasejs/9.19.0/firebase-firestore.js';

      // ❸で発行したfirebaseConfigを記述する
      const firebaseConfig = {
        apiKey: 'HOGEHOGE',
        authDomain: 'FUGAFUGA',
        projectId: 'HOGEHOGE',
        storageBucket: 'FUGAFUGA',
        messagingSenderId: 'HOGEHOGE',
        appId: 'FUGAFUGA',
        measurementId: 'PIYOPIYO'
      };
      const app = initializeApp(firebaseConfig);
      const messaging = getMessaging(app);

      document.querySelector('.push').addEventListener('click', requestPermission);

      function requestPermission() {
        if (!window.Notification) {
          return;
        }

        Notification.requestPermission().then((permission) => {
          if (permission === 'granted') {
            const vapidKey = 'HOGEHOGE'; // ❹でメモしたウェブプッシュ証明書の鍵ペアを記述する

            getToken(messaging, { vapidKey }).then(async (currentToken) => {
              if (currentToken) {
                await sendTokenToServer(currentToken);
                document.getElementById('token').value = currentToken;
              }
            }).catch((err) => {
              console.error(err);
            });
          }
        });
      }

      async function sendTokenToServer(currentToken) {
        const db = getFirestore(app);
        const ref = doc(db, 'pwa-token', currentToken);

        await setDoc(ref, {
          token: currentToken // tokenを保存する
        });
      }
    </script>
  </body>
</html>

public/manifest.json

{
  "$schema": "https://json.schemastore.org/web-manifest-combined.json",
  "name": "push-notification",
  "short_name": "push-notification",
  "start_url": ".",
  "display": "standalone",
  "background_color": "#000",
  "description": "プッシュ通知検証",
  "icons": [{
    "src": "./icon.png",
    "sizes": "192x192",
    "type": "image/png"
  }]
}

public/firebase-messaging-sw.js

importScripts('/__/firebase/9.19.0/firebase-app-compat.js');
importScripts('/__/firebase/9.19.0/firebase-messaging-compat.js');
importScripts('/__/firebase/init.js');

const messaging = firebase.messaging();

messaging.onBackgroundMessage((evt) => {
  console.log(evt);
});

functions/index.js

const admin = require('firebase-admin');
const functions = require('firebase-functions');

admin.apps.length ? admin.app() : admin.initializeApp({
  credential: admin.credential.applicationDefault()
});

exports.sendPushNotificationsUseAdmin = functions.https.onRequest(async (_, resp) => {
  const tokens = await getTokens();
  const maxLength = 500;
  const messagesList = [[]];

  tokens.forEach((token) => {
    messagesList[messagesList.length - 1].push({
      token,
      notification: {
        title: 'Push通知テスト',
        body: 'Push通知テスト'
      }
    });

    if (messagesList[messagesList.length - 1].length === maxLength) {
      messagesList.push([]);
    }
  });

  messagesList.forEach((messages) => {
    if (messages.length) {
      // admin.messaging().sendAll(messages);
      admin.messaging().sendEach(messages);
    }
  });

  return resp.send(JSON.stringify(messagesList));
});

async function getTokens() {
  const db = admin.firestore();
  const query = db.collection('pwa-token').where('token', '>', '');
  const snapshot = await query.get();
  const tokens = snapshot.docs.map((doc) => {
    return doc.get('token');
  });

  return tokens;
}

これで、HostingとFunctionsをデプロイし、sendPushNotificationsUseAdminを実行すればプッシュ通知が届くはずです。

tokenがひとつしか登録されていないのであれば、 admin.messaging().sendAll ではなく、admin.messaging().send で送信してしまえば良いのですが、今後tokenが複数登録されることを想定し、500件ずつsendAllsendEachで送信しています。(sendAllが廃止されたためsendEachに変更しました 2024.9.18 追記)

Messaging.send()

firebase.google.com

Messaging.sendAll() (廃止)

firebase.google.com

Messaging.sendEach()

firebase.google.com