ВозможностиОрганизации
Документация
    Ring Platform

    Децентрализованное самостроящееся будущее

    Войти
    Организации
    Возможности
    Магазин
    Документация
    Концепции платформы
    RING ЭкономикаSonoratek LLCГлобальное влияниеAI встречает Web3
    Начать
    Быстрый стартКалькуляторДорожная карта
    Конфиденциальность|Контакты
    v1.104.17|Sonoratek LLC

    Documentation

    Концепции, ценность и типичные сценарии клонирования — меньше кода.

    Добро пожаловать в Ring
    Краткий справочник
    С чего начать
    Предварительные требования
    Установка
    Первый успех
    Следующие шаги
    Функции
    Store
    Склад и остатки
    Управление вендорами
    Комиссии и расчёты
    SubscriptionConductor
    PaymentConductor
    Интеграция платежей
    Интеграция WayForPay
    Web3 Кошелек
    Реферальные коды (Refcodes)
    NFT Exhibition Marketplace
    Система Стейкинга Токенов
    Entities
    Opportunities
    Real-Time Messaging
    Peer Games
    Модуль Новостей - Цифровой Газетный Опыт
    Блоги участников
    Система Резервирования Имён Пользователей
    Научный редактор
    Notifications
    Push-уведомления через FCM (Ring)
    Email AI-CRM
    Протокол Tunnel
    Authentication
    Безопасность и соответствие
    Система локалей
    Мобильный Опыт
    Паттерны Оптимизации Производительности
    Примеры
    Quick Start
    Базовая настройка
    White Label
    Custom Branding
    Интеграция Web3
    Реальные Проекты
    Advanced Features
    Кастомизация
    Руководство по Ringization
    Branding
    Темы оформления
    Features
    Localization
    Настройка токеномики
    Web3
    Token launch jurisdictions
    Интеграции
    Ethereum-кошельки (Wagmi v3)
    Развёртывание
    Self-hosted развёртывание
    Vercel
    Docker
    Конфигурация Окружения
    Мониторинг и аналитика
    Оптимизация производительности
    Резервное копирование и восстановление
    Архитектура
    Data Model
    Security
    Real Time
    Архитектура PaymentConductor
    Бекенд Сервисы
    Интеграция Firebase
    Разработка
    Ring MCP Server

    Быстрый вход (CTO · аудиторы · агенты)

    Приветствие — миссия и аудитории
    Краткий справочник
    С чего начать
    Архитектура и Auth.js
    Режимы бэкенда и базы данных (DB_BACKEND_MODE)
    Self-hosted
    Ring MCP Tools
    Ring MCP Server
    Token economics
    Token launch jurisdictions
    Деплой (Docker · k8s)
    Безопасность и соответствие требованиям
    ringdom.org — база LegioX
    Исходный код — лицензия MIT (GitHub)

    Documentation

    Концепции, ценность и типичные сценарии клонирования — меньше кода.

    Добро пожаловать в Ring
    Краткий справочник
    С чего начать
    Предварительные требования
    Установка
    Первый успех
    Следующие шаги
    Функции
    Store
    Склад и остатки
    Управление вендорами
    Комиссии и расчёты
    SubscriptionConductor
    PaymentConductor
    Интеграция платежей
    Интеграция WayForPay
    Web3 Кошелек
    Реферальные коды (Refcodes)
    NFT Exhibition Marketplace
    Система Стейкинга Токенов
    Entities
    Opportunities
    Real-Time Messaging
    Peer Games
    Модуль Новостей - Цифровой Газетный Опыт
    Блоги участников
    Система Резервирования Имён Пользователей
    Научный редактор
    Notifications
    Push-уведомления через FCM (Ring)
    Email AI-CRM
    Протокол Tunnel
    Authentication
    Безопасность и соответствие
    Система локалей
    Мобильный Опыт
    Паттерны Оптимизации Производительности
    Примеры
    Quick Start
    Базовая настройка
    White Label
    Custom Branding
    Интеграция Web3
    Реальные Проекты
    Advanced Features
    Кастомизация
    Руководство по Ringization
    Branding
    Темы оформления
    Features
    Localization
    Настройка токеномики
    Web3
    Token launch jurisdictions
    Интеграции
    Ethereum-кошельки (Wagmi v3)
    Развёртывание
    Self-hosted развёртывание
    Vercel
    Docker
    Конфигурация Окружения
    Мониторинг и аналитика
    Оптимизация производительности
    Резервное копирование и восстановление
    Архитектура
    Data Model
    Security
    Real Time
    Архитектура PaymentConductor
    Бекенд Сервисы
    Интеграция Firebase
    Разработка
    Ring MCP Server

    Быстрый вход (CTO · аудиторы · агенты)

    Приветствие — миссия и аудитории
    Краткий справочник
    С чего начать
    Архитектура и Auth.js
    Режимы бэкенда и базы данных (DB_BACKEND_MODE)
    Self-hosted
    Ring MCP Tools
    Ring MCP Server
    Token economics
    Token launch jurisdictions
    Деплой (Docker · k8s)
    Безопасность и соответствие требованиям
    ringdom.org — база LegioX
    Исходный код — лицензия MIT (GitHub)

    Documentation

    Концепции, ценность и типичные сценарии клонирования — меньше кода.

    Добро пожаловать в Ring
    Краткий справочник
    С чего начать
    Предварительные требования
    Установка
    Первый успех
    Следующие шаги
    Функции
    Store
    Склад и остатки
    Управление вендорами
    Комиссии и расчёты
    SubscriptionConductor
    PaymentConductor
    Интеграция платежей
    Интеграция WayForPay
    Web3 Кошелек
    Реферальные коды (Refcodes)
    NFT Exhibition Marketplace
    Система Стейкинга Токенов
    Entities
    Opportunities
    Real-Time Messaging
    Peer Games
    Модуль Новостей - Цифровой Газетный Опыт
    Блоги участников
    Система Резервирования Имён Пользователей
    Научный редактор
    Notifications
    Push-уведомления через FCM (Ring)
    Email AI-CRM
    Протокол Tunnel
    Authentication
    Безопасность и соответствие
    Система локалей
    Мобильный Опыт
    Паттерны Оптимизации Производительности
    Примеры
    Quick Start
    Базовая настройка
    White Label
    Custom Branding
    Интеграция Web3
    Реальные Проекты
    Advanced Features
    Кастомизация
    Руководство по Ringization
    Branding
    Темы оформления
    Features
    Localization
    Настройка токеномики
    Web3
    Token launch jurisdictions
    Интеграции
    Ethereum-кошельки (Wagmi v3)
    Развёртывание
    Self-hosted развёртывание
    Vercel
    Docker
    Конфигурация Окружения
    Мониторинг и аналитика
    Оптимизация производительности
    Резервное копирование и восстановление
    Архитектура
    Data Model
    Security
    Real Time
    Архитектура PaymentConductor
    Бекенд Сервисы
    Интеграция Firebase
    Разработка
    Ring MCP Server

    Быстрый вход (CTO · аудиторы · агенты)

    Приветствие — миссия и аудитории
    Краткий справочник
    С чего начать
    Архитектура и Auth.js
    Режимы бэкенда и базы данных (DB_BACKEND_MODE)
    Self-hosted
    Ring MCP Tools
    Ring MCP Server
    Token economics
    Token launch jurisdictions
    Деплой (Docker · k8s)
    Безопасность и соответствие требованиям
    ringdom.org — база LegioX
    Исходный код — лицензия MIT (GitHub)
    Ring Logo

    Loading documentation...

    Preparing Ring content

    Ring Logo

    Loading documentation...

    Preparing Ring content

    Ring Logo

    Loading documentation...

    Preparing Ring content

    Peer Games

    Use Founder / Developer tabs in the docs sidebar to filter this page. Sidebar visibility is curated in lib/docs/audience-curated-docs.ts.

    Ring ships peer mini-games that reuse Tunnel fan-out, the interactive-message kit, and public-profile IA — without a GameConductor, parallel messaging stack, or stakes.

    SurfacePath
    Marketplace/games
    Mini-app/games/[slug]?session=
    Public availability/{username}/games
    Owner manage/profile/games (Member)
    Chat widgetmessage type / metadata.kind = game_request
    Incoming bannerMessagesShell and /games layout

    Titles (shipped): tic-tac-toe · chess · checkers. Catalog copy is localized via locales/*/modules/games.json (modules.games); English strings in catalog.ts remain FCM/history fallback.

    Why this matters for your clone

    Members bond faster when they can play without leaving your Ring. Challenges land as chat widgets and as a global IncomingGameBanner on Messages and /games — so invites are not missed outside the messenger thread.

    • Free Member perk — no Stars / credits / PaymentConductor stakes
    • Direct conversations only (same gate as 1:1 calls)
    • Call ↔ game mutex (cross-tab via BroadcastChannel): cannot accept a game while a call is active, and vice versa
    • Public Play with me only offers slugs the peer enabled on /{username}/games
    • Pending invites expire (15 minutes); idle active games clean up after 2 hours

    Operator checklist

    1. Confirm membership so owners can open /profile/games and publish availability (chess / checkers / tic-tac-toe).
    2. Smoke /games in each locale — catalog titles should localize (EN/UK/RU/ES/DE).
    3. From a direct chat, compose a game challenge → peer sees banner and widget.
    4. Accept → play moves → resign; both sides update without refresh.
    5. Confirm Phone/Video call blocks game accept while the call overlay is live (try a second browser tab too).
    6. Optional: leave a pending invite ~15 minutes (or run session-expiry cron) → banner and mini-app clear.

    Messaging

    Architecture

    Key modules

    PathRole
    features/peer-games/service.tsSessions + availability + createInvite SSOT
    features/peer-games/session-expiry.tsOrphan reclaim + pending/idle expiry
    features/peer-games/plugins/*-logic.tsServer-safe validate/apply (ttt, chess, checkers)
    features/peer-games/catalog.tsSlug registry (EN fallback)
    locales/*/modules/games.jsonCatalog + marketplace i18n (modGames)
    features/peer-games/lib/peer-game-mutex.tsCall/game busy + BroadcastChannel / Web Locks
    features/peer-games/lib/catalog-i18n.tsLocalized title/description helpers

    Related documentation

    Related documentation

    Real-Time Messaging

    Depends-on: game_request lives in the interactive-message kit and direct conversations.

    WebRTC Calls & STUNner TURN

    Same-workflow: shared call/game mutex; ICE route reused for DataChannel move hints.

    Tunnel Protocol

    Depends-on: conversation:*, game:{sessionId} ACL, and games:incoming fan-out.

    SubscriptionConductor

    Prerequisite: Member privileges gate /profile/games and challenge compose.

    Peer Games

    Use Founder / Developer tabs in the docs sidebar to filter this page. Sidebar visibility is curated in lib/docs/audience-curated-docs.ts.

    Ring ships peer mini-games that reuse Tunnel fan-out, the interactive-message kit, and public-profile IA — without a GameConductor, parallel messaging stack, or stakes.

    SurfacePath
    Marketplace/games
    Mini-app/games/[slug]?session=
    Public availability/{username}/games
    Owner manage/profile/games (Member)
    Chat widgetmessage type / metadata.kind = game_request
    Incoming bannerMessagesShell and /games layout

    Titles (shipped): tic-tac-toe · chess · checkers. Catalog copy is localized via locales/*/modules/games.json (modules.games); English strings in catalog.ts remain FCM/history fallback.

    Why this matters for your clone

    Members bond faster when they can play without leaving your Ring. Challenges land as chat widgets and as a global IncomingGameBanner on Messages and /games — so invites are not missed outside the messenger thread.

    • Free Member perk — no Stars / credits / PaymentConductor stakes
    • Direct conversations only (same gate as 1:1 calls)
    • Call ↔ game mutex (cross-tab via BroadcastChannel): cannot accept a game while a call is active, and vice versa
    • Public Play with me only offers slugs the peer enabled on /{username}/games
    • Pending invites expire (15 minutes); idle active games clean up after 2 hours

    Operator checklist

    1. Confirm membership so owners can open /profile/games and publish availability (chess / checkers / tic-tac-toe).
    2. Smoke /games in each locale — catalog titles should localize (EN/UK/RU/ES/DE).
    3. From a direct chat, compose a game challenge → peer sees banner and widget.
    4. Accept → play moves → resign; both sides update without refresh.
    5. Confirm Phone/Video call blocks game accept while the call overlay is live (try a second browser tab too).
    6. Optional: leave a pending invite ~15 minutes (or run session-expiry cron) → banner and mini-app clear.

    Messaging

    Architecture

    Key modules

    PathRole
    features/peer-games/service.tsSessions + availability + createInvite SSOT
    features/peer-games/session-expiry.tsOrphan reclaim + pending/idle expiry
    features/peer-games/plugins/*-logic.tsServer-safe validate/apply (ttt, chess, checkers)
    features/peer-games/catalog.tsSlug registry (EN fallback)
    locales/*/modules/games.jsonCatalog + marketplace i18n (modGames)
    features/peer-games/lib/peer-game-mutex.tsCall/game busy + BroadcastChannel / Web Locks
    features/peer-games/lib/catalog-i18n.tsLocalized title/description helpers

    Related documentation

    Related documentation

    Real-Time Messaging

    Depends-on: game_request lives in the interactive-message kit and direct conversations.

    WebRTC Calls & STUNner TURN

    Same-workflow: shared call/game mutex; ICE route reused for DataChannel move hints.

    Tunnel Protocol

    Depends-on: conversation:*, game:{sessionId} ACL, and games:incoming fan-out.

    SubscriptionConductor

    Prerequisite: Member privileges gate /profile/games and challenge compose.

    Peer Games

    Use Founder / Developer tabs in the docs sidebar to filter this page. Sidebar visibility is curated in lib/docs/audience-curated-docs.ts.

    Ring ships peer mini-games that reuse Tunnel fan-out, the interactive-message kit, and public-profile IA — without a GameConductor, parallel messaging stack, or stakes.

    SurfacePath
    Marketplace/games
    Mini-app/games/[slug]?session=
    Public availability/{username}/games
    Owner manage/profile/games (Member)
    Chat widgetmessage type / metadata.kind = game_request
    Incoming bannerMessagesShell and /games layout

    Titles (shipped): tic-tac-toe · chess · checkers. Catalog copy is localized via locales/*/modules/games.json (modules.games); English strings in catalog.ts remain FCM/history fallback.

    Why this matters for your clone

    Members bond faster when they can play without leaving your Ring. Challenges land as chat widgets and as a global IncomingGameBanner on Messages and /games — so invites are not missed outside the messenger thread.

    • Free Member perk — no Stars / credits / PaymentConductor stakes
    • Direct conversations only (same gate as 1:1 calls)
    • Call ↔ game mutex (cross-tab via BroadcastChannel): cannot accept a game while a call is active, and vice versa
    • Public Play with me only offers slugs the peer enabled on /{username}/games
    • Pending invites expire (15 minutes); idle active games clean up after 2 hours

    Operator checklist

    1. Confirm membership so owners can open /profile/games and publish availability (chess / checkers / tic-tac-toe).
    2. Smoke /games in each locale — catalog titles should localize (EN/UK/RU/ES/DE).
    3. From a direct chat, compose a game challenge → peer sees banner and widget.
    4. Accept → play moves → resign; both sides update without refresh.
    5. Confirm Phone/Video call blocks game accept while the call overlay is live (try a second browser tab too).
    6. Optional: leave a pending invite ~15 minutes (or run session-expiry cron) → banner and mini-app clear.

    Messaging

    Architecture

    Key modules

    PathRole
    features/peer-games/service.tsSessions + availability + createInvite SSOT
    features/peer-games/session-expiry.tsOrphan reclaim + pending/idle expiry
    features/peer-games/plugins/*-logic.tsServer-safe validate/apply (ttt, chess, checkers)
    features/peer-games/catalog.tsSlug registry (EN fallback)
    locales/*/modules/games.jsonCatalog + marketplace i18n (modGames)
    features/peer-games/lib/peer-game-mutex.tsCall/game busy + BroadcastChannel / Web Locks
    features/peer-games/lib/catalog-i18n.tsLocalized title/description helpers

    Related documentation

    Related documentation

    Real-Time Messaging

    Depends-on: game_request lives in the interactive-message kit and direct conversations.

    WebRTC Calls & STUNner TURN

    Same-workflow: shared call/game mutex; ICE route reused for DataChannel move hints.

    Tunnel Protocol

    Depends-on: conversation:*, game:{sessionId} ACL, and games:incoming fan-out.

    SubscriptionConductor

    Prerequisite: Member privileges gate /profile/games and challenge compose.

    Conversations and interactive widgets (game_request in the shared kit).

    WebRTC Calls

    Shared call/game mutex; ICE helper reused for optimistic DataChannel move hints.

    Membership

    Member gate for publishing availability and composing challenges.

    Public Profile

    /{username}/games sits beside Player and Gallery on the public profile.

    features/peer-games/hooks/use-peer-game-datachannel.tsOptimistic move hints (ring-peer-game-moves)
    app/_actions/peer-games.tsServer actions
    app/api/conversations/[id]/game-invite/route.tsThin HTTP twin → createInvite
    app/api/cron/peer-game-session-expiry/route.tsProcessConductor pipeline
    app/api/tunnel/subscribe/route.tsHTTP game:* participant ACL
    lib/tunnel/native-ws/attach.tsWS game:* participant ACL (mirrors HTTP)
    IncomingGameBannergames:incoming + game:{id} lifecycle clear

    Persistence

    Migration data/migrations/043_peer_games.sql:

    • peer_game_sessions — indexes on conversation / challenger / peer / status / slug / messageId / updated_at
    • user_peer_games — per-owner enabledSlugs (unique on ownerId)

    Tunnel channels (verified)

    ChannelEvents
    conversation:{id}game:invite, game:accept, game:decline, game:resign, game:move, game:expire
    game:{sessionId}game:accept, game:decline, game:resign, game:move, game:expire, game:dc-signal
    user inboxgames:incoming (invite, accepted, terminal)

    Subscribe ACL: deny-by-default for game:{sessionId} on HTTP and native WS — only DB participants (getSessionForParticipant). Spectate deferred.

    Banner clear: game:expire / game:decline / game:accept on the session channel, plus games:incoming with terminal or accepted (covers subscribe race after invite).

    Notifications & FCM

    NotificationType.GAME_REQUEST / GAME_UPDATED. Offline push: after !deliveredLive, wait ~500ms and recheck getTunnelHub().isUserConnected(peer) before createNotification + FCM. Soft Web Audio chime on the banner only — never call ringtone / setPeerCallBusy.

    Hub isUserConnected is process-local. Multi-pod false offline FCM is a known residual — shared presence is backlog.

    Mutex, dedupe, boards

    • Mutex: setPeerCallBusy / setPeerGameBusy with BroadcastChannel (+ optional navigator.locks)
    • Invite dedupe: Redis SET NX PX inside createInvite (claim-before; releaseNx on failure) — Server Action + HTTP + Play-with-me share SSOT
    • Chess: FEN SSOT (key={fen}); drops return false
    • Checkers: English draughts, forced capture, single jump per turn (multi-jump backlog)

    Session expiry

    Pipeline peer-game-session-expiry:

    CaseTTLResult
    Orphan pending (no messageId)~2mReclaim
    Pending invite15mDeclined + updateMessageLocked
    Active idle2hResigned/completed + Tunnel notify

    Schedulers: Vercel */5 (vercel.json) · k3s-or CronJobs infrastructure/k3s-or/ring-platform-org/cronjob-peer-game-session-expiry.yaml (+ sibling cronjob-close-expired-polls.yaml, /bin/sh so CRON_SECRET expands). Apply on cluster when promoting k8s prod.

    DataChannel (optimistic only)

    RTCDataChannel label ring-peer-game-moves (ordered) + fetchIceServers. Peer envelopes are hints; validateMove / Tunnel game:move / DB remain SSOT. Reject → rollback to last authoritative hydrate. ICE restart under flaky nets is backlog; Tunnel path remains the fallback.

    Server actions

    createGameRequest · acceptGameRequest · declineGameRequest · resignPeerGameAction · submitPeerGameMoveAction · getPeerGameSessionAction · updateEnabledGamesAction · challengeUserToGameAction

    Smoke / soak

    Structural: npx tsx scripts/smoke-peer-games-soak.cts. Plugin unit tests under __tests__/features/peer-games/plugins.test.ts. Live Member E2E checklist: AI-CONTEXT/concepts/peer-games-live-e2e-soak-2026-07-24.json (empire Reggie propagate blocked until signoff).

    Public Profile Pages

    Next-step: public /{username}/games beside Player and Gallery.

    Push Notifications with FCM (Ring-Powered)

    See-also: createInvite FCM GAME_REQUEST after Tunnel presence grace.

    Notifications

    See-also: GAME_REQUEST and GAME_UPDATED preference types.

    text
    
    createGameRequest / POST …/game-invite / challengeUserToGameAction
      → PeerGameService.createInvite (SSOT)
      → Redis SET NX claim peer-game:invite:{from}:{to}:{slug} (Map fallback)
      → create session (peer_game_sessions) FIRST
      → send game_request message → link messageId
      → on failure: delete session + releaseNx
      → publishToChannel(conversation:…, game:invite)
      → publishToUserTunnel(peer, games:incoming)
      → if !deliveredLive: sleep ~500ms; FCM GAME_REQUEST only if still !isUserConnected
    accept / decline / resign / submitMove
      → participant ACL + plugin validateMove/applyMove
      → updateMessageLocked on linked widget metadata
      → game:* on game:{sessionId} AND conversation:{id}
      → decline/expire also publish terminal on games:incoming (banner clear)
    client mini-app
      → hydrate DB SSOT → useTunnelChannel(game:{id})
      → optional RTCDataChannel optimistic hints (fetchIceServers); server remains SSOT

    Conversations and interactive widgets (game_request in the shared kit).

    WebRTC Calls

    Shared call/game mutex; ICE helper reused for optimistic DataChannel move hints.

    Membership

    Member gate for publishing availability and composing challenges.

    Public Profile

    /{username}/games sits beside Player and Gallery on the public profile.

    features/peer-games/hooks/use-peer-game-datachannel.tsOptimistic move hints (ring-peer-game-moves)
    app/_actions/peer-games.tsServer actions
    app/api/conversations/[id]/game-invite/route.tsThin HTTP twin → createInvite
    app/api/cron/peer-game-session-expiry/route.tsProcessConductor pipeline
    app/api/tunnel/subscribe/route.tsHTTP game:* participant ACL
    lib/tunnel/native-ws/attach.tsWS game:* participant ACL (mirrors HTTP)
    IncomingGameBannergames:incoming + game:{id} lifecycle clear

    Persistence

    Migration data/migrations/043_peer_games.sql:

    • peer_game_sessions — indexes on conversation / challenger / peer / status / slug / messageId / updated_at
    • user_peer_games — per-owner enabledSlugs (unique on ownerId)

    Tunnel channels (verified)

    ChannelEvents
    conversation:{id}game:invite, game:accept, game:decline, game:resign, game:move, game:expire
    game:{sessionId}game:accept, game:decline, game:resign, game:move, game:expire, game:dc-signal
    user inboxgames:incoming (invite, accepted, terminal)

    Subscribe ACL: deny-by-default for game:{sessionId} on HTTP and native WS — only DB participants (getSessionForParticipant). Spectate deferred.

    Banner clear: game:expire / game:decline / game:accept on the session channel, plus games:incoming with terminal or accepted (covers subscribe race after invite).

    Notifications & FCM

    NotificationType.GAME_REQUEST / GAME_UPDATED. Offline push: after !deliveredLive, wait ~500ms and recheck getTunnelHub().isUserConnected(peer) before createNotification + FCM. Soft Web Audio chime on the banner only — never call ringtone / setPeerCallBusy.

    Hub isUserConnected is process-local. Multi-pod false offline FCM is a known residual — shared presence is backlog.

    Mutex, dedupe, boards

    • Mutex: setPeerCallBusy / setPeerGameBusy with BroadcastChannel (+ optional navigator.locks)
    • Invite dedupe: Redis SET NX PX inside createInvite (claim-before; releaseNx on failure) — Server Action + HTTP + Play-with-me share SSOT
    • Chess: FEN SSOT (key={fen}); drops return false
    • Checkers: English draughts, forced capture, single jump per turn (multi-jump backlog)

    Session expiry

    Pipeline peer-game-session-expiry:

    CaseTTLResult
    Orphan pending (no messageId)~2mReclaim
    Pending invite15mDeclined + updateMessageLocked
    Active idle2hResigned/completed + Tunnel notify

    Schedulers: Vercel */5 (vercel.json) · k3s-or CronJobs infrastructure/k3s-or/ring-platform-org/cronjob-peer-game-session-expiry.yaml (+ sibling cronjob-close-expired-polls.yaml, /bin/sh so CRON_SECRET expands). Apply on cluster when promoting k8s prod.

    DataChannel (optimistic only)

    RTCDataChannel label ring-peer-game-moves (ordered) + fetchIceServers. Peer envelopes are hints; validateMove / Tunnel game:move / DB remain SSOT. Reject → rollback to last authoritative hydrate. ICE restart under flaky nets is backlog; Tunnel path remains the fallback.

    Server actions

    createGameRequest · acceptGameRequest · declineGameRequest · resignPeerGameAction · submitPeerGameMoveAction · getPeerGameSessionAction · updateEnabledGamesAction · challengeUserToGameAction

    Smoke / soak

    Structural: npx tsx scripts/smoke-peer-games-soak.cts. Plugin unit tests under __tests__/features/peer-games/plugins.test.ts. Live Member E2E checklist: AI-CONTEXT/concepts/peer-games-live-e2e-soak-2026-07-24.json (empire Reggie propagate blocked until signoff).

    Public Profile Pages

    Next-step: public /{username}/games beside Player and Gallery.

    Push Notifications with FCM (Ring-Powered)

    See-also: createInvite FCM GAME_REQUEST after Tunnel presence grace.

    Notifications

    See-also: GAME_REQUEST and GAME_UPDATED preference types.

    text
    
    createGameRequest / POST …/game-invite / challengeUserToGameAction
      → PeerGameService.createInvite (SSOT)
      → Redis SET NX claim peer-game:invite:{from}:{to}:{slug} (Map fallback)
      → create session (peer_game_sessions) FIRST
      → send game_request message → link messageId
      → on failure: delete session + releaseNx
      → publishToChannel(conversation:…, game:invite)
      → publishToUserTunnel(peer, games:incoming)
      → if !deliveredLive: sleep ~500ms; FCM GAME_REQUEST only if still !isUserConnected
    accept / decline / resign / submitMove
      → participant ACL + plugin validateMove/applyMove
      → updateMessageLocked on linked widget metadata
      → game:* on game:{sessionId} AND conversation:{id}
      → decline/expire also publish terminal on games:incoming (banner clear)
    client mini-app
      → hydrate DB SSOT → useTunnelChannel(game:{id})
      → optional RTCDataChannel optimistic hints (fetchIceServers); server remains SSOT

    Conversations and interactive widgets (game_request in the shared kit).

    WebRTC Calls

    Shared call/game mutex; ICE helper reused for optimistic DataChannel move hints.

    Membership

    Member gate for publishing availability and composing challenges.

    Public Profile

    /{username}/games sits beside Player and Gallery on the public profile.

    features/peer-games/hooks/use-peer-game-datachannel.tsOptimistic move hints (ring-peer-game-moves)
    app/_actions/peer-games.tsServer actions
    app/api/conversations/[id]/game-invite/route.tsThin HTTP twin → createInvite
    app/api/cron/peer-game-session-expiry/route.tsProcessConductor pipeline
    app/api/tunnel/subscribe/route.tsHTTP game:* participant ACL
    lib/tunnel/native-ws/attach.tsWS game:* participant ACL (mirrors HTTP)
    IncomingGameBannergames:incoming + game:{id} lifecycle clear

    Persistence

    Migration data/migrations/043_peer_games.sql:

    • peer_game_sessions — indexes on conversation / challenger / peer / status / slug / messageId / updated_at
    • user_peer_games — per-owner enabledSlugs (unique on ownerId)

    Tunnel channels (verified)

    ChannelEvents
    conversation:{id}game:invite, game:accept, game:decline, game:resign, game:move, game:expire
    game:{sessionId}game:accept, game:decline, game:resign, game:move, game:expire, game:dc-signal
    user inboxgames:incoming (invite, accepted, terminal)

    Subscribe ACL: deny-by-default for game:{sessionId} on HTTP and native WS — only DB participants (getSessionForParticipant). Spectate deferred.

    Banner clear: game:expire / game:decline / game:accept on the session channel, plus games:incoming with terminal or accepted (covers subscribe race after invite).

    Notifications & FCM

    NotificationType.GAME_REQUEST / GAME_UPDATED. Offline push: after !deliveredLive, wait ~500ms and recheck getTunnelHub().isUserConnected(peer) before createNotification + FCM. Soft Web Audio chime on the banner only — never call ringtone / setPeerCallBusy.

    Hub isUserConnected is process-local. Multi-pod false offline FCM is a known residual — shared presence is backlog.

    Mutex, dedupe, boards

    • Mutex: setPeerCallBusy / setPeerGameBusy with BroadcastChannel (+ optional navigator.locks)
    • Invite dedupe: Redis SET NX PX inside createInvite (claim-before; releaseNx on failure) — Server Action + HTTP + Play-with-me share SSOT
    • Chess: FEN SSOT (key={fen}); drops return false
    • Checkers: English draughts, forced capture, single jump per turn (multi-jump backlog)

    Session expiry

    Pipeline peer-game-session-expiry:

    CaseTTLResult
    Orphan pending (no messageId)~2mReclaim
    Pending invite15mDeclined + updateMessageLocked
    Active idle2hResigned/completed + Tunnel notify

    Schedulers: Vercel */5 (vercel.json) · k3s-or CronJobs infrastructure/k3s-or/ring-platform-org/cronjob-peer-game-session-expiry.yaml (+ sibling cronjob-close-expired-polls.yaml, /bin/sh so CRON_SECRET expands). Apply on cluster when promoting k8s prod.

    DataChannel (optimistic only)

    RTCDataChannel label ring-peer-game-moves (ordered) + fetchIceServers. Peer envelopes are hints; validateMove / Tunnel game:move / DB remain SSOT. Reject → rollback to last authoritative hydrate. ICE restart under flaky nets is backlog; Tunnel path remains the fallback.

    Server actions

    createGameRequest · acceptGameRequest · declineGameRequest · resignPeerGameAction · submitPeerGameMoveAction · getPeerGameSessionAction · updateEnabledGamesAction · challengeUserToGameAction

    Smoke / soak

    Structural: npx tsx scripts/smoke-peer-games-soak.cts. Plugin unit tests under __tests__/features/peer-games/plugins.test.ts. Live Member E2E checklist: AI-CONTEXT/concepts/peer-games-live-e2e-soak-2026-07-24.json (empire Reggie propagate blocked until signoff).

    Public Profile Pages

    Next-step: public /{username}/games beside Player and Gallery.

    Push Notifications with FCM (Ring-Powered)

    See-also: createInvite FCM GAME_REQUEST after Tunnel presence grace.

    Notifications

    See-also: GAME_REQUEST and GAME_UPDATED preference types.

    text
    
    createGameRequest / POST …/game-invite / challengeUserToGameAction
      → PeerGameService.createInvite (SSOT)
      → Redis SET NX claim peer-game:invite:{from}:{to}:{slug} (Map fallback)
      → create session (peer_game_sessions) FIRST
      → send game_request message → link messageId
      → on failure: delete session + releaseNx
      → publishToChannel(conversation:…, game:invite)
      → publishToUserTunnel(peer, games:incoming)
      → if !deliveredLive: sleep ~500ms; FCM GAME_REQUEST only if still !isUserConnected
    accept / decline / resign / submitMove
      → participant ACL + plugin validateMove/applyMove
      → updateMessageLocked on linked widget metadata
      → game:* on game:{sessionId} AND conversation:{id}
      → decline/expire also publish terminal on games:incoming (banner clear)
    client mini-app
      → hydrate DB SSOT → useTunnelChannel(game:{id})
      → optional RTCDataChannel optimistic hints (fetchIceServers); server remains SSOT
    1. Документация
    2. /Функции
    3. /Peer Games

    Обновлено 24 июл. 2026 г.5 мин прослушивания

    1. Документация
    2. /Функции
    3. /Peer Games

    Обновлено 24 июл. 2026 г.5 мин прослушивания

    1. Документация
    2. /Функции
    3. /Peer Games

    Обновлено 24 июл. 2026 г.5 мин прослушивания