Підготовка контенту платформи Ring
Підготовка контенту платформи Ring
Підготовка контенту платформи Ring
Ring Platform stores FCM tokens in your primary database (PostgreSQL or Firestore by backend mode), one row per user per device. React apps register tokens via a Server Action; non-React clients use a rate-limited API. Both paths call the same server-only DB layer so behavior and security stay consistent everywhere. How modes differ (Postgres-primary vs full Firebase, DB_HYBRID_MODE): Backend modes and databases.
"Ring-powered" FCM means a single, backend-agnostic design:
fcm_tokens table (or collection) holds all registration tokens. Stored payload lives in a JSONB data column where applicable (PostgreSQL). No secondary cache; the database is the registry.(user_id, device_fingerprint) ensures one record per device. The token value is not unique, so FCM token rotation updates the same row instead of creating duplicates.upsertFcmToken Server Action. The server derives user_id from auth() only; the client never sends it.POST /api/notifications/fcm/register with the same payload. This endpoint is rate-limited per user (for example, 10 requests per minute).status (active | stale | invalid) and invalidatedAt. Reactive cleanup runs when FCM returns errors for a token; scheduled jobs mark stale tokens and optionally hard-delete after a retention window.DB_BACKEND_MODE to PostgreSQL (e.g. k8s-postgres-fcm, ) or Firestore (). One code path; no branching in the Server Action or API route.Ring Platform stores FCM tokens in your primary database (PostgreSQL or Firestore by backend mode), one row per user per device. React apps register tokens via a Server Action; non-React clients use a rate-limited API. Both paths call the same server-only DB layer so behavior and security stay consistent everywhere. How modes differ (Postgres-primary vs full Firebase, DB_HYBRID_MODE): Backend modes and databases.
"Ring-powered" FCM means a single, backend-agnostic design:
fcm_tokens table (or collection) holds all registration tokens. Stored payload lives in a JSONB data column where applicable (PostgreSQL). No secondary cache; the database is the registry.(user_id, device_fingerprint) ensures one record per device. The token value is not unique, so FCM token rotation updates the same row instead of creating duplicates.upsertFcmToken Server Action. The server derives user_id from auth() only; the client never sends it.POST /api/notifications/fcm/register with the same payload. This endpoint is rate-limited per user (for example, 10 requests per minute).status (active | stale | invalid) and invalidatedAt. Reactive cleanup runs when FCM returns errors for a token; scheduled jobs mark stale tokens and optionally hard-delete after a retention window.DB_BACKEND_MODE to PostgreSQL (e.g. k8s-postgres-fcm, ) or Firestore (). One code path; no branching in the Server Action or API route.Ring Platform stores FCM tokens in your primary database (PostgreSQL or Firestore by backend mode), one row per user per device. React apps register tokens via a Server Action; non-React clients use a rate-limited API. Both paths call the same server-only DB layer so behavior and security stay consistent everywhere. How modes differ (Postgres-primary vs full Firebase, DB_HYBRID_MODE): Backend modes and databases.
"Ring-powered" FCM means a single, backend-agnostic design:
fcm_tokens table (or collection) holds all registration tokens. Stored payload lives in a JSONB data column where applicable (PostgreSQL). No secondary cache; the database is the registry.(user_id, device_fingerprint) ensures one record per device. The token value is not unique, so FCM token rotation updates the same row instead of creating duplicates.upsertFcmToken Server Action. The server derives user_id from auth() only; the client never sends it.POST /api/notifications/fcm/register with the same payload. This endpoint is rate-limited per user (for example, 10 requests per minute).status (active | stale | invalid) and invalidatedAt. Reactive cleanup runs when FCM returns errors for a token; scheduled jobs mark stale tokens and optionally hard-delete after a retention window.DB_BACKEND_MODE to PostgreSQL (e.g. k8s-postgres-fcm, ) or Firestore (). One code path; no branching in the Server Action or API route.supabase-fcmfirebase-full/firebase-messaging-sw.js.Use the Server Action as the primary way to register and refresh FCM tokens from any React or Next.js client. This path is not rate-limited like the API route and keeps user id server-derived only.
React apps should use the Server Action as the primary path. The API route is for non-React clients and is rate-limited.
supabase-fcmfirebase-full/firebase-messaging-sw.js.Use the Server Action as the primary way to register and refresh FCM tokens from any React or Next.js client. This path is not rate-limited like the API route and keeps user id server-derived only.
React apps should use the Server Action as the primary path. The API route is for non-React clients and is rate-limited.
supabase-fcmfirebase-full/firebase-messaging-sw.js.Use the Server Action as the primary way to register and refresh FCM tokens from any React or Next.js client. This path is not rate-limited like the API route and keeps user id server-derived only.
React apps should use the Server Action as the primary path. The API route is for non-React clients and is rate-limited.
Obtain a stable device fingerprint
Generate or load a stable value (for example, crypto.randomUUID() stored in localStorage) and reuse it on every load and on token refresh. The server uses (user_id, device_fingerprint) to upsert one row per device.
Request permission and get the FCM token
In a 'use client' component (for example inside a notification provider), call Notification.requestPermission(). When granted, call getToken(messaging, { vapidKey }) from the Firebase Messaging SDK. Do not call getToken() in Server Components; they have no browser context.
Call the Server Action to register the token
Invoke upsertFcmToken({ token, deviceFingerprint, deviceInfo, platform: 'web' }). Do not pass userId; the server derives it from auth().
Handle token refresh
When the Firebase SDK emits a new token (for example via onTokenRefresh), call the same Server Action again with the new token and the same deviceFingerprint. The shared DB layer updates the existing row for that user and device.
Obtain a stable device fingerprint
Generate or load a stable value (for example, crypto.randomUUID() stored in localStorage) and reuse it on every load and on token refresh. The server uses (user_id, device_fingerprint) to upsert one row per device.
Request permission and get the FCM token
In a 'use client' component (for example inside a notification provider), call Notification.requestPermission(). When granted, call getToken(messaging, { vapidKey }) from the Firebase Messaging SDK. Do not call getToken() in Server Components; they have no browser context.
Call the Server Action to register the token
Invoke upsertFcmToken({ token, deviceFingerprint, deviceInfo, platform: 'web' }). Do not pass userId; the server derives it from auth().
Handle token refresh
When the Firebase SDK emits a new token (for example via onTokenRefresh), call the same Server Action again with the new token and the same deviceFingerprint. The shared DB layer updates the existing row for that user and device.
Obtain a stable device fingerprint
Generate or load a stable value (for example, crypto.randomUUID() stored in localStorage) and reuse it on every load and on token refresh. The server uses (user_id, device_fingerprint) to upsert one row per device.
Request permission and get the FCM token
In a 'use client' component (for example inside a notification provider), call Notification.requestPermission(). When granted, call getToken(messaging, { vapidKey }) from the Firebase Messaging SDK. Do not call getToken() in Server Components; they have no browser context.
Call the Server Action to register the token
Invoke upsertFcmToken({ token, deviceFingerprint, deviceInfo, platform: 'web' }). Do not pass userId; the server derives it from auth().
Handle token refresh
When the Firebase SDK emits a new token (for example via onTokenRefresh), call the same Server Action again with the new token and the same deviceFingerprint. The shared DB layer updates the existing row for that user and device.
Example: registering and refreshing the token from a client component.
Mobile apps or other non-React clients register tokens by calling the same contract over HTTP.
Endpoint: POST /api/notifications/fcm/register
Request body:
token (string, required) — FCM registration token from the client SDK.deviceFingerprint (string, required) — Stable device identifier (e.g. UUID in local storage). Maximum 128 characters; alphanumeric, hyphens, and underscores only.deviceInfo (object, optional) — Arbitrary device metadata (e.g. platform, userAgent).Authentication: Required (session cookie or Bearer token). The server uses the authenticated user id; do not send userId in the body.
Responses:
200 — { "success": true }400 — Validation error (e.g. missing or invalid deviceFingerprint)401 — Not authenticated429 — Rate limit exceeded (see Retry-After header)500 — Server errorThis endpoint is rate-limited per user (e.g. 10 requests per minute). Use the Server Action from React apps to avoid consuming the API rate limit.
Example with cURL (replace session cookie or use Bearer token as required by your auth setup):
Example: registering and refreshing the token from a client component.
Mobile apps or other non-React clients register tokens by calling the same contract over HTTP.
Endpoint: POST /api/notifications/fcm/register
Request body:
token (string, required) — FCM registration token from the client SDK.deviceFingerprint (string, required) — Stable device identifier (e.g. UUID in local storage). Maximum 128 characters; alphanumeric, hyphens, and underscores only.deviceInfo (object, optional) — Arbitrary device metadata (e.g. platform, userAgent).Authentication: Required (session cookie or Bearer token). The server uses the authenticated user id; do not send userId in the body.
Responses:
200 — { "success": true }400 — Validation error (e.g. missing or invalid deviceFingerprint)401 — Not authenticated429 — Rate limit exceeded (see Retry-After header)500 — Server errorThis endpoint is rate-limited per user (e.g. 10 requests per minute). Use the Server Action from React apps to avoid consuming the API rate limit.
Example with cURL (replace session cookie or use Bearer token as required by your auth setup):
Example: registering and refreshing the token from a client component.
Mobile apps or other non-React clients register tokens by calling the same contract over HTTP.
Endpoint: POST /api/notifications/fcm/register
Request body:
token (string, required) — FCM registration token from the client SDK.deviceFingerprint (string, required) — Stable device identifier (e.g. UUID in local storage). Maximum 128 characters; alphanumeric, hyphens, and underscores only.deviceInfo (object, optional) — Arbitrary device metadata (e.g. platform, userAgent).Authentication: Required (session cookie or Bearer token). The server uses the authenticated user id; do not send userId in the body.
Responses:
200 — { "success": true }400 — Validation error (e.g. missing or invalid deviceFingerprint)401 — Not authenticated429 — Rate limit exceeded (see Retry-After header)500 — Server errorThis endpoint is rate-limited per user (e.g. 10 requests per minute). Use the Server Action from React apps to avoid consuming the API rate limit.
Example with cURL (replace session cookie or use Bearer token as required by your auth setup):
Split configuration so that only public, safe values are exposed to the client.
VAPID private key and Firebase service account credentials must never be exposed to the client. Keep them server-only.
Split configuration so that only public, safe values are exposed to the client.
VAPID private key and Firebase service account credentials must never be exposed to the client. Keep them server-only.
Split configuration so that only public, safe values are exposed to the client.
VAPID private key and Firebase service account credentials must never be exposed to the client. Keep them server-only.
| Purpose | Variables | Where |
|---|---|---|
| Client (browser / app) | 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 | Client-safe; used by Firebase client SDK and service worker |
| Server (token store, FCM send) | FIREBASE_SERVICE_ACCOUNT_* or equivalent service account JSON path; DB_BACKEND_MODE (e.g. k8s-postgres-fcm, firebase-full, supabase-fcm) | Server-only; used by BackendSelector and Firebase Admin SDK |
| Purpose | Variables | Where |
|---|---|---|
| Client (browser / app) | 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 | Client-safe; used by Firebase client SDK and service worker |
| Server (token store, FCM send) | FIREBASE_SERVICE_ACCOUNT_* or equivalent service account JSON path; DB_BACKEND_MODE (e.g. k8s-postgres-fcm, firebase-full, supabase-fcm) | Server-only; used by BackendSelector and Firebase Admin SDK |
| Purpose | Variables | Where |
|---|---|---|
| Client (browser / app) | 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 | Client-safe; used by Firebase client SDK and service worker |
| Server (token store, FCM send) | FIREBASE_SERVICE_ACCOUNT_* or equivalent service account JSON path; DB_BACKEND_MODE (e.g. k8s-postgres-fcm, firebase-full, supabase-fcm) | Server-only; used by BackendSelector and Firebase Admin SDK |
On logout or when the user disables push:
deleteToken(messaging) from the Firebase client SDK, then unregister on the server. Send DELETE /api/notifications/fcm/register with body { "deviceFingerprint": "same-uuid-as-register" }. Idempotent; safe to call even if the row is already removed or invalidated.DELETE request with the same deviceFingerprint used at registration.The server marks the row as status: 'invalid' and sets invalidatedAt; delivery to that token is stopped.
On logout or when the user disables push:
deleteToken(messaging) from the Firebase client SDK, then unregister on the server. Send DELETE /api/notifications/fcm/register with body { "deviceFingerprint": "same-uuid-as-register" }. Idempotent; safe to call even if the row is already removed or invalidated.DELETE request with the same deviceFingerprint used at registration.The server marks the row as status: 'invalid' and sets invalidatedAt; delivery to that token is stopped.
On logout or when the user disables push:
deleteToken(messaging) from the Firebase client SDK, then unregister on the server. Send DELETE /api/notifications/fcm/register with body { "deviceFingerprint": "same-uuid-as-register" }. Idempotent; safe to call even if the row is already removed or invalidated.DELETE request with the same deviceFingerprint used at registration.The server marks the row as status: 'invalid' and sets invalidatedAt; delivery to that token is stopped.
| Issue | Cause | What to do |
|---|---|---|
| Permission denied or token not received | User denied notification permission or permission not yet requested | Request permission before calling getToken(); handle permission === 'denied' in the UI |
| 429 on register | Rate limit exceeded | Use the Server Action from React apps; for API clients, respect Retry-After and reduce register frequency |
| Invalid deviceFingerprint | Validation failure | Use a string of length 1–128 with only letters, numbers, hyphens, and underscores |
| 401 on register | Not authenticated | Ensure a valid session (cookie or Bearer) is sent; do not send userId in the body |
| Issue | Cause | What to do |
|---|---|---|
| Permission denied or token not received | User denied notification permission or permission not yet requested | Request permission before calling getToken(); handle permission === 'denied' in the UI |
| 429 on register | Rate limit exceeded | Use the Server Action from React apps; for API clients, respect Retry-After and reduce register frequency |
| Invalid deviceFingerprint | Validation failure | Use a string of length 1–128 with only letters, numbers, hyphens, and underscores |
| 401 on register | Not authenticated | Ensure a valid session (cookie or Bearer) is sent; do not send userId in the body |
| Issue | Cause | What to do |
|---|---|---|
| Permission denied or token not received | User denied notification permission or permission not yet requested | Request permission before calling getToken(); handle permission === 'denied' in the UI |
| 429 on register | Rate limit exceeded | Use the Server Action from React apps; for API clients, respect Retry-After and reduce register frequency |
| Invalid deviceFingerprint | Validation failure | Use a string of length 1–128 with only letters, numbers, hyphens, and underscores |
| 401 on register | Not authenticated | Ensure a valid session (cookie or Bearer) is sent; do not send userId in the body |
fcm_tokens (e.g. data/schema.sql, data/migrations/fcm_jsonb_schema.sql) in your Ring cloneAI-LEGIOX/legiox-truth-lens/fcm-specialist.json) for token lifecycle, reactive cleanup, and send path detailsfcm_tokens (e.g. data/schema.sql, data/migrations/fcm_jsonb_schema.sql) in your Ring cloneAI-LEGIOX/legiox-truth-lens/fcm-specialist.json) for token lifecycle, reactive cleanup, and send path detailsfcm_tokens (e.g. data/schema.sql, data/migrations/fcm_jsonb_schema.sql) in your Ring cloneAI-LEGIOX/legiox-truth-lens/fcm-specialist.json) for token lifecycle, reactive cleanup, and send path details