Push-уведомления с Firebase

Push-уведомления являются отличной альтернативой уведомлениям по электронной почте, нет необходимости в шаге проверки, улучшается UX и повышается вовлеченность пользователей в приложение.

Требования к push-уведомлениям

  • Созданный проект Firebase
  • ID проекта, можно найти в настройках проекта → вкладка Общие
  • Ключ сервера для отправки push-уведомлений (используется на бэкенде)
  • Публичный ключ Vapid, находится на странице Настройки проекта → Cloud Messaging → Сертификаты Web Push (используется на front-end).
  • Конфигурация Firebase, находится в Настройки проекта → Общие → Ваши приложения
  • Рабочий сервис обмена сообщениями Firebase
  • HTTPS-соединение (localhost для локальной разработки)

Вспомогательные функции

getToken

  • генерирует уникальный токен для браузера или получает уже сгенерированный токен
  • запрашивает разрешение на получение push-уведомлений
  • запускает работника службы сообщений Firebase.

Если пользователь блокирует push-уведомления, возникает ошибка FirebaseError с кодом messaging/permission-blocked. Если браузер пользователя не поддерживает API, необходимые для использования Firebase SDK, возникает ошибка FirebaseError с кодом messaging/unsupported-browser. Токен доступа становится недействительным, когда пользователь вручную блокирует уведомления в настройках браузера.

isSupported

  • проверяет, поддерживаются ли все необходимые API для push-уведомлений.
  • возвращает Promise<boolean>.

Его следует использовать в хуках useEffect.

import { isSupported } from 'firebase/messaging';

// ...
useEffect(() => {
  isSupported()
    .then((isAvailable) => {
      if (isAvailable) {
        // ...
      }
    })
    .catch(console.error);
}, []);
// ...
Вход в полноэкранный режим Выход из полноэкранного режима

initializeApp

  • должен быть вызван до запуска приложения
import { initializeApp } from 'firebase/app';
import { getMessaging, getToken, isSupported } from 'firebase/messaging';
import { firebaseConfig } from 'constants/config';

export const initializeFirebase = () => initializeApp(firebaseConfig);

export const getTokenForPushNotifications = async () => {
  const messaging = getMessaging();
  const token = await getToken(messaging, {
    vapidKey: process.env.NEXT_PUBLIC_VAPID_KEY,
  });
  return token;
}
Вход в полноэкранный режим Выход из полноэкранного режима

Рабочий орган службы сообщений Firebase

Для обработки фоновых уведомлений необходимо зарегистрировать следующий рабочий сервис. Пользовательский обработчик notificationclick должен быть реализован до импорта библиотек firebase, нижеприведенная реализация открывает новое окно с заданным URL, если оно еще не открыто. Firebase автоматически проверяет наличие рабочих служб в /firebase-messaging-sw.js, поэтому он должен быть общедоступен.

// /firebase-messaging-sw.js
/* eslint-disable no-unused-vars */
self.addEventListener("notificationclick", (event) => {
  event.notification.close();
  const DEFAULT_URL = "<URL>";
  const url =
    event.notification?.data?.FCM_MSG?.notification?.click_action ||
    DEFAULT_URL;
  event.waitUntil(
    clients.matchAll({ type: "window" }).then((clientsArray) => {
      const hadWindowToFocus = clientsArray.some((windowClient) =>
        windowClient.url === url ? (windowClient.focus(), true) : false
      );
      if (!hadWindowToFocus)
        clients
          .openWindow(url)
          .then((windowClient) => (windowClient ? windowClient.focus() : null));
    })
  );
});

let messaging = null;
try {
  if (typeof importScripts === "function") {
    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: "xxxxxx",
      authDomain: "xxxxxx",
      projectId: "xxxxxx",
      storageBucket: "xxxxxx",
      messagingSenderId: "xxxxxx",
      appId: "xxxxxx",
      measurementId: "xxxxxx",
    });

    messaging = firebase.messaging();
  }
} catch (error) {
  console.error(error);
}
Вход в полноэкранный режим Выход из полноэкранного режима

Ключи сервера

Ключ сервера для API v1 может быть получен из JSON-файла ключа учетной записи сервиса, в этом случае JSON-файл должен быть закодирован и сохранен в переменной окружения, чтобы предотвратить раскрытие учетных данных в кодовой базе репозитория. JSON-файл ключа учетной записи сервиса можно загрузить, нажав кнопку Generate new private key на вкладке Project settings → Service accounts. Ключ сервера для устаревшего API можно найти на вкладке Настройки проекта → Cloud Messaging → Cloud Messaging API (Legacy), если он включен.

import * as serviceAccountKey from './serviceAccountKey.json';

const encodedServiceAccountKey = Buffer.from(
  JSON.stringify(serviceAccountKey),
).toString('base64');

process.env.SERVICE_ACCOUNT_KEY = encodedServiceAccountKey;
Вход в полноэкранный режим Выход из полноэкранного режима
import 'dotenv/config';
import * as googleAuth from 'google-auth-library';

(async () => {
  const serviceAccountKeyEncoded = process.env.SERVICE_ACCOUNT_KEY;
  const serviceAccountKeyDecoded = JSON.parse(
    Buffer.from(serviceAccountKeyEncoded, 'base64').toString('ascii'),
  );
  const jwt = new googleAuth.JWT(
    serviceAccountKeyDecoded.client_email,
    null,
    serviceAccountKeyDecoded.private_key,
    ['https://www.googleapis.com/auth/firebase.messaging'],
    null,
  );
  const tokens = await jwt.authorize();
  const authorizationHeader = `Bearer ${tokens.access_token}`;
  console.log(authorizationHeader);
})();
Войти в полноэкранный режим Выход из полноэкранного режима

Ручная отправка push-уведомления

URL иконки должен быть покрыт HTTPS, чтобы иконка могла быть правильно показана в уведомлении.

  • наследие
curl --location --request POST 'https://fcm.googleapis.com/fcm/send' 
--header 'Authorization: key=<SERVER_KEY>' 
--header 'Content-Type: application/json' 
--data-raw '{
  "notification": {
    "title": "Push notifications with Firebase",
    "body": "Push notifications with Firebase body",
    "click_action": "http://localhost:3000",
    "icon": "https://picsum.photos/200"
  },
  "to": "<TOKEN>"
}'
Войти в полноэкранный режим Выход из полноэкранного режима

Ответ содержит ключ success со значением 1 при успешной отправке push-уведомления. Ответ содержит ключ failure со значением 1 при неудачной отправке push-уведомления. В этом случае ключ results представляет собой массив с объектами ошибок, некоторые из имен ошибок: InvalidRegistration и NotRegistered.

  • API v1
curl --location --request POST 'https://fcm.googleapis.com/v1/projects/<PROJECT_ID>/messages:send' 
--header 'Authorization: Bearer <TOKEN_DERIVED_FROM_SERVICE_ACCOUNT_KEY>' 
--header 'Content-Type: application/json' 
--data-raw '{
  "message": {
    "notification": {
      "title": "Push notifications with Firebase",
      "body": "Push notifications with Firebase body"
    },
    "webpush": {
      "fcmOptions": {
        "link": "http://localhost:3000"
      },
      "notification": {
        "icon": "https://picsum.photos/200"
      }
    },
    "token": "<TOKEN>"
  }
}'
Вход в полноэкранный режим Выйти из полноэкранного режима

Успешный ответ возвращает JSON с ключом name, который представляет идентификатор уведомления в формате projects/{project_id}/messages/{message_id}. Ошибка с кодом 400 возникает, когда тело запроса не соответствует действительности. Ошибка с кодом 401 возникает, если срок действия производного токена истек.

Оцените статью
Procodings.ru
Добавить комментарий