---
title: "Push-сповіщення через FCM (Ring)"
description: "Налаштування веб- та кроссплатформних push-сповіщень за допомогою Firebase Cloud Messaging: Server Action для токенів, спільний шар БД та керування життєвим циклом."
locale: "uk"
---
# Push-сповіщення через FCM (Ring)

  Dual-stack truth (FCM Admin + RFC `web-push`, iOS Home Screen, empty RFC-on-Chrome no-op) lives on the [English article](/docs/features/push-notifications-fcm.md). This UK page still describes the 2026-02 FCM-only register path.

Ring Platform зберігає FCM-токени в основній базі даних (PostgreSQL або Firestore залежно від режиму бекенду) — по одному запису на користувача та пристрій. React-додатки реєструють токени через Server Action; інші клієнти використовують API з обмеженням частоти. Обидва шляхи викликають один і той самий серверний шар БД, тому поводження та безпека скрізь однакові.

## Огляд: логіка FCM у Ring

«Ring-powered» FCM — це єдина, незалежна від бекенду схема:

- **Сховище токенів як джерело істини** — Таблиця (або колекція) `fcm_tokens` зберігає всі токени реєстрації. Корисне навантаження зберігається в колонці JSONB `data` де застосовно (PostgreSQL). Окремого кешу немає; реєстром слугує БД.
- **Один рядок на пристрій і користувача** — Складне унікальне обмеження по `(user_id, device_fingerprint)` дає один запис на пристрій. Значення токена не унікальне, тому при ротації FCM-токена оновлюється той самий рядок, а не створюється дублікат.
- **React-додаток → Server Action** — Основний шлях для браузера та React Native web — Server Action `upsertFcmToken`. Ідентифікатор користувача сервер бере лише з `auth()`; клієнт його не передає.
- **Інші клієнти → API** — Мобільні додатки або зовнішні сервіси викликають `POST /api/notifications/fcm/register` з тим самим тілом запиту. Цей endpoint обмежений по частоті на користувача (наприклад, 10 запитів на хвилину).
- **Життєвий цикл у даних** — У кожному рядку зберігаються `status` (`active` | `stale` | `invalid`) та `invalidatedAt`. Реактивне очищення спрацьовує при помилках FCM по токену; за розкладом позначаються застарілі токени та за потреби виконується жорстке видалення.
- **Режим бекенду** — Сховище токенів не прив’язане до конкретного бекенду. BackendSelector направляє запити по `DB_BACKEND_MODE` у PostgreSQL (наприклад `k8s-postgres-fcm`, `supabase-fcm`) або Firestore (`firebase-full`). Один код; розгалужень у Server Action чи API немає.

```mermaid
flowchart LR
  subgraph client["Client"]
    React["React app\n(useFCMToken)"]
    NonReact["Non-React\n(mobile, API)"]
  end
  subgraph server["Server"]
    SA["Server Action\nupsertFcmToken"]
    API["POST .../fcm/register\n(rate-limited)"]
    DBLayer["upsertFcmTokenForUser\n(server-only)"]
    Selector["BackendSelector"]
  end
  subgraph storage["Storage"]
    PG[(PostgreSQL\nfcm_tokens)]
    FS[(Firestore\nfcm_tokens)]
  end
  React -->|primary| SA
  NonReact --> API
  SA --> DBLayer
  API --> DBLayer
  DBLayer --> Selector
  Selector -->|k8s / supabase| PG
  Selector -->|firebase-full| FS
```

## Передумови

- **Аутентифікація** — Виконано вхід (сесія Auth.js). Реєстрація токена завжди прив’язана до аутентифікованого користувача; сервер не довіряє ідентифікатору користувача від клієнта.
- **Web Push (браузер)** — HTTPS (або localhost під час розробки), підтримка Web Push у браузері, зареєстрований service worker та Firebase client SDK. Файл service worker зазвичай доступний за `/firebase-messaging-sw.js`.
- **API-клієнти** — Діюча сесія (cookie або Bearer) та той самий контракт тіла запиту (token, deviceFingerprint, deviceInfo).

## React-додаток: шлях через Server Action

Використовуйте Server Action як основний спосіб реєстрації та оновлення FCM-токенів з будь-якого React- або Next.js-клієнта. Цей шлях не обмежений по частоті, на відміну від API, і ідентифікатор користувача лишається лише на сервері.

React-додаткам слід використовувати Server Action як основний шлях. API призначено для інших клієнтів і обмежений по частоті.

**Отримайте стабільний device fingerprint**

Згенеруйте або завантажте стабільне значення (наприклад, `crypto.randomUUID()` з `localStorage`) і використовуйте його при кожному завантаженні та при оновленні токена. Сервер за парою `(user_id, device_fingerprint)` виконує upsert одного рядка на пристрій.

**Запросіть дозвіл і отримайте FCM-токен**

У компоненті з `'use client'` (наприклад усередині провайдера сповіщень) викличте `Notification.requestPermission()`. Після дозволу викличте `getToken(messaging, { vapidKey })` з Firebase Messaging SDK. Не викликайте `getToken()` у Server Components — у них немає контексту браузера.

**Викличте Server Action для реєстрації токена**

Викличте `upsertFcmToken({ token, deviceFingerprint, deviceInfo, platform: 'web' })`. Не передавайте `userId`; сервер бере його з `auth()`.

**Обробка оновлення токена**

Коли Firebase SDK видає новий токен (наприклад через `onTokenRefresh`), знову викличте той самий Server Action з новим токеном і тим самим `deviceFingerprint`. Спільний шар БД оновить існуючий рядок для цього користувача та пристрою.

Приклад: реєстрація та оновлення токена в клієнтському компоненті.

{`import { getMessaging, getToken } from 'firebase/messaging'
import { getFcmVapidKey, getFirebaseClientApp } from '@/lib/firebase-client'

const firebaseApp = getFirebaseClientApp()
const vapidKey = getFcmVapidKey()
if (!firebaseApp || !vapidKey) return
const token = await getToken(getMessaging(firebaseApp), { vapidKey })
// Persist: POST /api/notifications/fcm/register (not upsertFcmToken Server Action from React)`}

## Інші клієнти: API

Мобільні додатки та інші клієнти без React реєструють токени через той самий контракт по HTTP.

**Endpoint:** `POST /api/notifications/fcm/register`

**Тіло запиту:**

- `token` (string, обов’язково) — FCM registration token з клієнтського SDK.
- `deviceFingerprint` (string, обов’язково) — Стабільний ідентифікатор пристрою (наприклад UUID у local storage). Не більше 128 символів; лише літери, цифри, дефіс та підкреслення.
- `deviceInfo` (object, необов’язково) — Довільні метадані пристрою (наприклад platform, userAgent).

**Аутентифікація:** Обов’язкова (session cookie або Bearer). Сервер використовує ідентифікатор аутентифікованого користувача; не передавайте `userId` у тілі.

**Відповіді:**

- `200` — `{ "success": true }`
- `400` — Помилка валідації (наприклад відсутній або невірний `deviceFingerprint`)
- `401` — Не аутентифіковано
- `429` — Перевищено ліміт запитів (заголовок `Retry-After`)
- `500` — Помилка сервера

Цей endpoint обмежений по частоті на користувача (наприклад 10 запитів на хвилину). React-оболонка має викликати `POST /api/notifications/fcm/register` (не Server Action) — див. [EN FCM](/docs/features/push-notifications-fcm.md).

Приклад з cURL (підставте cookie сесії або Bearer згідно з вашою налаштуванням auth):

{`curl -X POST https://your-app.com/api/notifications/fcm/register \\
  -H "Content-Type: application/json" \\
  -H "Cookie: your-session-cookie" \\
  -d '{
    "token": "FCM_REGISTRATION_TOKEN",
    "deviceFingerprint": "uuid-from-device",
    "deviceInfo": { "platform": "ios", "appVersion": "1.0.0" }
  }'`}

## Змінні середовища

Налаштування розділені так, щоб на клієнт потрапляли лише публічні та безпечні значення.

Приватний ключ VAPID та облікові дані Firebase service account не повинні потрапляти на клієнт. Зберігайте їх лише на сервері.

| Призначення | Змінні | Де |
|-------------|--------|-----|
| Клієнт (браузер / додаток) | `NEXT_PUBLIC_FIREBASE_API_KEY`, `NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN`, `NEXT_PUBLIC_FIREBASE_PROJECT_ID`, `NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID`, `NEXT_PUBLIC_FIREBASE_APP_ID`, `NEXT_PUBLIC_FIREBASE_VAPID_KEY` | Безпечні для клієнта; використовуються Firebase client SDK та service worker |
| Сервер (сховище токенів, відправка FCM) | `FIREBASE_SERVICE_ACCOUNT_*` або шлях до JSON сервісного акаунта; `DB_BACKEND_MODE` (наприклад `k8s-postgres-fcm`, `firebase-full`, `supabase-fcm`) | Лише сервер; використовуються BackendSelector та Firebase Admin SDK |

## Зняття реєстрації та вихід

При виході або вимкненні push користувачем:

- **Браузер / React** — Викличте `deleteToken(messaging)` з Firebase client SDK, потім зніміть реєстрацію на сервері: `DELETE /api/notifications/fcm/register` з тілом `{ "deviceFingerprint": "same-uuid-as-register" }`. Ідемпотентно; можна викликати навіть якщо запис уже видалено або інвалідовано.
- **Інші клієнти** — Той самий запит `DELETE` з тим самим `deviceFingerprint`, що й при реєстрації.

Сервер позначає рядок як `status: 'invalid'` і встановлює `invalidatedAt`; доставка на цей токен припиняється.

## Усунення несправностей

| Проблема | Причина | Дія |
|----------|---------|-----|
| Permission denied або токен не отримано | Користувач відхилив сповіщення або дозвіл ще не запитували | Запросіть дозвіл перед викликом `getToken()`; обробляйте `permission === 'denied'` в інтерфейсі |
| 429 при реєстрації | Перевищено ліміт запитів | Використовуйте Server Action з React; для API-клієнтів дотримуйтесь `Retry-After` та зменшіть частоту реєстрації |
| Невірний deviceFingerprint | Помилка валідації | Рядок довжиною 1–128 символів: лише літери, цифри, дефіс та підкреслення |
| 401 при реєстрації | Не аутентифіковано | Переконайтесь, що надсилається діюча сесія (cookie або Bearer); не передавайте `userId` у тілі |

## Див. також

- [Сповіщення](/docs/features/notifications.md) — Типи сповіщень, канали та налаштування
- Схема та міграції для `fcm_tokens` (наприклад `data/schema.sql`, `data/migrations/fcm_jsonb_schema.sql`) у вашому клоні Ring
- Truth lens FCM-фахівця (`AI-LEGIOX/legiox-truth-lens/fcm-specialist.json`) — життєвий цикл токенів, реактивне очищення та шлях відправки
