Техническая спецификация
Commit: ce9317496b
Обзор
Синхронизация аккаунта между устройствами через привязку соцсетей (Google, Apple, Facebook). Игрок привязывает аккаунт к соцсети в настройках, после чего может восстановить прогресс на другом устройстве. При конфликте — выбор между локальной и серверной версией. Одновременная игра на двух устройствах невозможна — активная сессия одна, предыдущая кикается.
1. Флоу работы фичи
Инициализация
LoginAuth.Login()→POST /auth/deviceс Device ID- Сервер ищет Device ID в
AuthProviders, создаёт/находит аккаунт - Создаёт сессию в Redis через
SessionService.CreateSession() - Генерирует JWT с session claims (
sid,scat) - Клиент сохраняет JWT,
SessionManager.SetSessionFromToken(jwt)извлекает SessionId
Основной флоу (привязка соцсети)
SocialLinkManager.Link(type, token)→POST /auth/linkSocial- Сервер валидирует OAuth-токен через
IOAuthService - Ищет
AuthProviderс данным ProviderUid - NewLink — соцсеть свободна → создаёт
AuthProvider, клиент вызываетMarkAsLinked() - SameLink — уже привязана к текущему → ничего не делает
- OtherLink — привязана к другому → клиент загружает данные linked через
FetchCloudSave(), показывает диалог выбора - Игрок выбирает:
KeepCurrentProgress()(Current) илиRestoreProgress()(Linked) →POST /auth/relinkSocial
Жизненный цикл сессии
┌─────────────────────────────────────────────────────────────────┐
│ ЛОГИН (POST /auth/device) │
│ → SessionService.CreateSession(playerId, deviceId) │
│ → Всегда создаёт новую сессию (перезаписывает старую) │
│ → Если был другой девайс: SignalR SessionKicked │
│ → JWT содержит session claims (sid, scat) │
├─────────────────────────────────────────────────────────────────┤
│ SAVE (POST /save/save) │
│ → SessionId извлекается из JWT claims │
│ → Для клиентов >= 3.0: проверяет владение сессией │
│ → Новая сессия может перезаписать сейв старой (по scat) │
│ → При несовпадении: NoSession / SessionMismatch │
├─────────────────────────────────────────────────────────────────┤
│ КИК СЕССИИ (SignalR) │
│ → Сервер отправляет SessionKicked при логине с другого девайса │
│ → Клиент показывает диалог и закрывает приложение │
├─────────────────────────────────────────────────────────────────┤
│ ОБНАРУЖЕНИЕ НЕВАЛИДНОЙ СЕССИИ │
│ → HTTP 401 ответ от сервера → NotifySessionInvalid() │
│ → SaveStatus.NoSession/SessionMismatch → NotifySessionInvalid()│
│ → SignalR SessionKicked → NotifySessionKicked(reason) │
└─────────────────────────────────────────────────────────────────┘
Три точки обнаружения невалидной сессии:
- HTTP 401 (
ServerHttpRequests.cs): при получении 401 →SessionManager.NotifySessionInvalid() - Save rejection (
SaveRequests.cs): приSaveStatus.NoSessionилиSessionMismatch→SessionManager.NotifySessionInvalid() - SignalR kick (
SignalRequests.cs): при полученииSessionKickedс совпадающим SessionId →SessionManager.NotifySessionKicked(reason)
Таблица состояний
| Действие | Device текущего | Соцсети текущего | Device linked | Соцсети linked |
|---|---|---|---|---|
| Первый вход | Создаётся | — | — | — |
| Привязка соцсети | Без изменений | Добавляется | — | — |
| Relink (Linked) | → linked | → linked | Остаётся (multi-device) | Остаются |
| Relink (Current) | Остаётся | Остаются | Остаётся | Одна → current |
| Отвязка соцсети | Без изменений | Удаляется | — | — |
Пояснение:
- Linked — все провайдеры текущего переносятся на linked, Device linked остаётся (multi-device)
- Current — только одна соцсеть (через которую конфликт) переносится на current
Офлайн (pause/resume)
При сворачивании/разворачивании приложения (ApplicationLifecycleHandler.cs):
- Pause: PlayerProfileSaver.Save(skipCloudSave: true) — только локальное сохранение
- Resume: переподключение SignalR с существующим JWT (сессия не пересоздаётся)
Relink — серверная логика разрешения конфликтов
При OtherLink клиент показывает диалог выбора и вызывает POST /auth/relinkSocial.
ChosenAccount = Linked (переход на привязанный аккаунт):
1. Находит AuthProvider для соцсети → получает linkedPlayerId
2. Проверяет linkedPlayerId != currentPlayerId
3. Переносит все провайдеры текущего аккаунта на linked:
- Device: перепривязывается (оба Device остаются — multi-device), устанавливается PreviousPlayerId
- Соцсети: переносятся, если у linked нет такого типа
4. Возвращает JWT и PlayerId linked-аккаунта
ChosenAccount = Current (оставить текущий аккаунт):
1. Проверяет linkedPlayerId != currentPlayerId
2. Удаляет AuthProvider соцсети у linked
3. Создаёт AuthProvider соцсети у текущего
4. Возвращает JWT и PlayerId текущего аккаунта
Важно:
Linkedпереносит все провайдеры.Currentпереносит только одну соцсеть (ту, через которую конфликт).
Действия клиента после Relink:
1. LoginAuth.UpdateAuthData(jwt, playerId) — обновляет credentials
2. FetchCloudSave() → ServerHttpRequests.LoadPlayerData() — загружает сейв
3. PlayerProfileSaver.SaveToPlayerPrefs() — сохраняет локально
4. SocialLinkManager.RestartGame() — перезапускает игру
Кик сессии
При логине с другого устройства (POST /auth/device):
1. SessionService.CreateSession() перезаписывает сессию в Redis
2. Если предыдущий DeviceId отличается → NotifySessionKicked() через SignalR
3. SignalRequests на кикнутом устройстве проверяет совпадение SessionId
4. SessionHandler показывает диалог → приложение закрывается
Тестовые сценарии: qa.md
Edge cases
| Ситуация | Поведение |
|---|---|
| Два девайса, второй логинится | Первый получает SignalR SessionKicked → диалог → выход |
| Первый девайс офлайн при кике | SignalR не доставлен. При следующем запросе получит 401 или SessionMismatch |
| Save с устаревшей сессией | Сервер отклоняет (SessionMismatch), клиент вызывает NotifySessionInvalid() |
| Save без сессии | Сервер отклоняет (NoSession), клиент вызывает NotifySessionInvalid() |
| Social login | JWT без session claims, сохранение в облако невозможно до Device login |
API контракт
Эндпоинты
| Endpoint | Метод | Описание | Авторизация |
|---|---|---|---|
/auth/device |
POST | Вход по Device ID, создание сессии | Нет |
/auth/social |
POST | Вход через соцсеть (Google/Apple/Facebook) | Нет |
/auth/linkSocial |
POST | Привязка соцсети с автолинком (возвращает OtherLink при конфликте) |
JWT |
/auth/relinkSocial |
POST | Разрешение конфликта при привязке | JWT |
/auth/unlinkSocial |
POST | Отвязка соцсети от аккаунта | JWT |
/auth/linkedSocials |
POST | Список привязанных соцсетей (для синхронизации UI) | JWT |
/auth/checkSocial |
POST | Проверка привязки соцсети (диагностика, требует OAuth токен) | JWT |
/auth/cleardevice/{deviceId} |
POST | Удаление привязки Device (только для разработки) | AdminAuth |
/save/save |
POST | Сохранение данных игрока (проверяет сессию для >= 3.0) | JWT |
/save/load |
POST | Загрузка данных игрока | JWT |
/save/changeowner |
POST | Смена владельца сейва после relink (синхронизация) | JWT |
/save/snapshot |
POST | Создание снэпшота профиля | JWT |
/save/delete |
POST | Удаление данных игрока | JWT |
Примечание:
/auth/socialвозвращает JWT без session claims. Только/auth/deviceсоздаёт сессию и возвращает JWT с SessionId.
Обратная совместимость
Проблема
Старые клиенты (версия < 3.0) не знают о механизме сессий. Если сервер будет требовать сессию для всех клиентов, старые версии не смогут сохранять прогресс.
Решение
Save микросервис проверяет версию клиента и требует сессию только для новых клиентов:
// Server/Save/Controllers/SaveController.cs
private const int ClientSessionMinVersion = 3000;
private static bool IsSessionRequired(string? clientVersion)
{
if (string.IsNullOrEmpty(clientVersion))
{
return false; // Нет версии → старый клиент → сессия не требуется
}
return CompareVersions(clientVersion, ClientSessionMinVersion) >= 0;
}
Как передаётся версия клиента
Клиент передаёт версию приложения в HTTP-заголовке version для всех запросов:
// Assets/Data/Scripts/Meta/Server/ServerHttpRequests.cs
Version = GetAppVersionString(); // при создании
www.SetRequestHeader("version", Version ?? ""); // при каждом запросе
Сервер читает версию из заголовка:
var clientVersion = Request.Headers["version"].FirstOrDefault();
if (IsSessionRequired(clientVersion))
{
// Извлечь SessionId из JWT claims и проверить...
}
Как передаётся SessionId
SessionId не передаётся отдельным полем — он встроен в JWT токен как claim sid. Save сервер извлекает его из JWT middleware:
var sessionId = GetSessionId(); // из JWT claim "sid"
var sessionCreatedAt = GetSessionCreatedAt(); // из JWT claim "scat"
Таблица совместимости
| Версия клиента | Заголовок version |
Требуется сессия | Поведение |
|---|---|---|---|
| < 3.0 | "2.12.0" |
Нет | Сохранение работает без SessionId |
| >= 3.0 | "3.0.0" |
Да | Требуется валидный SessionId в JWT |
| Отсутствует | пустой / null | Нет | Legacy режим, сессия не требуется |
Миграция
При выходе версии 3.0:
- Существующие игроки продолжают играть на старых клиентах без проблем
- Новые клиенты получают полную защиту сессий
- После полного обновления базы игроков константу ClientSessionMinVersion можно удалить
2. Архитектура
graph TD
subgraph client-social["Клиент — Social"]
SocialLinkManager -->|Link/Relink/Unlink| ServerHttpRequests
SocialLinkManager --> SocialLinksComponent
SocialLinkManager --> SocialNetworks
SocialNetworks --> ISocialNetwork
ISocialNetwork -.-> AppleSocialNetwork
ISocialNetwork -.-> GoogleSocialNetwork
ISocialNetwork -.-> FacebookSocialNetwork
end
subgraph client-auth["Клиент — Auth"]
LoginAuth -->|POST /auth/device| ServerHttpRequests
LoginAuth -->|POST /auth/social| ServerHttpRequests
LoginAuth --> SessionManager
SessionManager --> TokenService
end
subgraph client-session["Клиент — Session"]
SessionHandler --> SessionManager
SignalRequests -->|SessionKicked| SessionManager
SaveRequests -->|NoSession/Mismatch| SessionManager
ServerHttpRequests -->|401| SessionManager
end
subgraph server-main["Сервер — Main :5000"]
AuthController --> SessionService
AuthController --> IOAuthService
AuthController --> JwtService
SessionService --> Redis
AuthController --> DB[(PostgreSQL)]
end
subgraph server-save["Сервер — Save :5001"]
SaveController --> JwtService
SaveController --> SessionService
SaveController --> DB
end
ServerHttpRequests -->|HTTP + JWT| AuthController
ServerHttpRequests -->|HTTP + JWT| SaveController
SignalRequests -->|SignalR| AuthController
Точка входа — LoginAuth (аутентификация) и SocialLinkManager (привязка соцсетей). SocialNetworks управляет SDK-реализациями провайдеров. Сессии контролируются через SessionManager на клиенте и SessionService (Redis) на сервере. Все HTTP-запросы проходят через ServerHttpRequests, кик сессий — через SignalR (SignalRequests).
Базовые правила
- Один аккаунт = несколько устройств, одна активная сессия
- Соцсеть = ключ для переноса между устройствами
- При переносе соцсеть перепривязывается к выбранному аккаунту
- Каждый запрос к серверу проверяет JWT токен
- Multi-Device: несколько устройств привязываются к одному аккаунту через Relink, Device не удаляется — оба работают
- Сессии и кик: при логине создаётся сессия в Redis, предыдущая перезаписывается — активное устройство получает SignalR
SessionKickedи закрывается - Версионирование: клиенты >= 3.0 требуют сессию для облачных сохранений, старые (< 3.0) работают без сессий
Разделение Main / Save
Сессии реализованы по принципу session-in-JWT — SessionId и время создания сессии встраиваются в JWT токен при логине. Отдельных HTTP-эндпоинтов для управления сессиями нет.
MAIN (5000) — аутентификация и сессии:
├── POST /auth/device — логин + создание сессии в Redis + JWT с session claims
└── SignalR SessionKicked — уведомление кикнутого устройства
SAVE (5001) — только данные:
├── POST /save/save — извлекает SessionId из JWT, проверяет, сохраняет
└── POST /save/load — загружает
Хранение сессий (Redis)
Сессии хранятся в Redis Hash, не в PostgreSQL:
Ключ: session:{playerId}
Поля:
sid — SessionId (GUID)
did — DeviceId
cat — CreatedAt (Unix timestamp)
3. Сцены и ассеты
[нет данных]
4. Конфиги
[нет данных]
5. Модели данных
DB модели
AuthPlayer (Server/Main/Model/DB/AuthPlayer.cs):
| Поле | Тип | Описание |
|---|---|---|
Id |
string |
PlayerId, 16 hex chars (GeneratePlayerId) |
CreatedAt |
DateTime |
Время создания |
AuthProviders |
List<AuthProvider> |
Навигационное свойство |
AuthProvider (Server/Main/Model/DB/AuthProvider.cs):
| Поле | Тип | Описание |
|---|---|---|
Id |
int |
PK |
PlayerId |
string |
FK → AuthPlayer |
ProviderType |
AuthProviderType |
Device, Google, Apple, Facebook |
ProviderUid |
string |
Внешний ID провайдера |
CreatedAt |
DateTime |
Время создания |
PreviousPlayerId |
string? |
Для отслеживания Relink (Device) |
Примечание:
AuthPlayerне содержит полей сессии. Вся информация о сессиях хранится в Redis.
API модели — запросы
DeviceAuthRequest:
DeviceId: string
SocialAuthRequest (для /auth/social):
ProviderType: AuthProviderType (Google, Apple, Facebook)
Token: string (OAuth токен от провайдера)
LinkSocialRequest (для /auth/linkSocial):
ProviderType: AuthProviderType (Google, Apple, Facebook)
Token: string (OAuth токен от провайдера)
RelinkSocialRequest:
ProviderType: AuthProviderType
Token: string
ChosenAccount: ChosenAccountType (Current = 0, Linked = 1)
DeviceId: string
UnlinkSocialRequest:
ProviderType: AuthProviderType
CheckSocialRequest:
ProviderType: AuthProviderType
Token: string
API модели — ответы
AuthResponse:
Token: string (JWT, при Device-логине содержит session claims)
PlayerId: string
RelinkFromPlayerId: string (ID предыдущего аккаунта, если Device был перепривязан через Relink)
LinkSocialResponse (для /auth/linkSocial):
Result: LinkSocialResult (NewLink | SameLink | OtherLink | Error)
Jwt: string
PlayerId: string
ProviderUid: string (ID пользователя у провайдера)
LinkedJwt: string (JWT привязанного аккаунта, только при OtherLink)
LinkedPlayerId: string (ID привязанного аккаунта, только при OtherLink)
Message: string
Примечание: При
OtherLinkсервер возвращаетLinkedJwtиLinkedPlayerId. Клиент загружает полные данные профиля черезFetchCloudSave()из Save микросервиса.
RelinkSocialResponse:
Success: bool
Jwt: string
PlayerId: string
ProviderUid: string (ID пользователя у провайдера)
Message: string
UnlinkSocialResponse:
Success: bool
Message: string
CheckSocialResponse:
Result: LinkSocialResult (NewLink | SameLink | OtherLink | Error)
LinkedJwt: string (JWT привязанного аккаунта, при OtherLink)
LinkedPlayerId: string (ID привязанного аккаунта, при OtherLink)
LinkedSocialsResponse:
Providers: List<AuthProviderType> (список привязанных соцсетей)
ChangeOwnerResponse (для /save/changeowner):
Success: bool
Error: string
OwnerChanged: bool
PreviousSessionId: string
HasSave: bool
Version: int
Timestamp: long
Name: string
Level: int
Gems: int
Модели сессий — клиент
Assets/Data/Scripts/Meta/Server/ServerHttpRequestModels.cs:
public class SessionInfo
{
public string SessionId;
public string DeviceId;
}
public class SessionKickedMessage
{
public string PlayerId;
public string SessionId;
public string Reason;
public long Timestamp;
}
Модели сессий — сервер
Server/Main/Services/SessionService.cs:
public class SessionInfo
{
public string? SessionId { get; set; }
public string? DeviceId { get; set; }
public long CreatedAtUtcTimestamp { get; set; }
}
public class CreateSessionResult
{
public bool Success { get; set; }
public SessionInfo? CreatedSession { get; set; }
public SessionInfo? PreviousSession { get; set; }
public string? KickedSessionId { get; set; }
public string? Message { get; set; }
}
TokenInfo (JWT)
Server/Shared/JwtService.cs:
public class TokenInfo
{
public string PlayerId { get; set; }
public string? SessionId { get; set; }
public long? SessionCreatedAt { get; set; }
public bool HasSession => !string.IsNullOrEmpty(SessionId) && SessionCreatedAt.HasValue;
}
JWT Claims
При Device-логине JWT содержит дополнительные claims:
sid — SessionId (GUID)
scat — SessionCreatedAt (Unix timestamp)
Клиент извлекает SessionId из JWT через TokenService.Parse(token) и SessionManager.SetSessionFromToken(token).
Enum-ы
AuthProviderType:
| Значение | Код |
|----------|-----|
| Device | 0 |
| Google | 1 |
| Apple | 2 |
| Facebook | 3 |
LinkSocialResult:
| Значение | Код | Описание |
|----------|-----|----------|
| NewLink | 0 | Соцсеть успешно привязана (была свободна) |
| SameLink | 1 | Соцсеть уже привязана к текущему аккаунту |
| OtherLink | 2 | Соцсеть привязана к другому аккаунту (конфликт) |
| Error | 3 | Ошибка валидации токена |
ChosenAccountType:
| Значение | Код |
|----------|-----|
| Current | 0 |
| Linked | 1 |
SaveStatus:
| Значение | Код |
|----------|-----|
| Saved | 0 |
| Conflict | 1 |
| NoSession | 2 |
| SessionMismatch | 3 |
6. Префабы
[нет данных]
7. Скрипты
Client — Auth
LoginAuth.cs
Assets/Data/Scripts/Meta/Server/LoginAuth.cs
- Основной класс аутентификации. Поддерживает вход по Device ID и через соцсеть. Управляет JWT-токеном и PlayerId, предоставляет доступ к
SessionManager. Включает миграцию v3.0 для принудительного перелогина при отсутствии SessionId.
Login(callback) → POST /auth/device
LoginBySocial(providerType, token, callback) → POST /auth/social
UpdateAuthData(jwt, playerId) — обновление credentials после relink
ClearAuthData() — очистка JWT и PlayerId
TokenClearIfOldVersion() — миграция v3.0: очистка токена без SessionId
Session — свойство: SessionManager
LoggedIn — bool, есть ли валидный токен
LastLoginTokenUpdated — bool, обновился ли токен при последнем логине
event Action OnLoginSuccessNotify; // успешный логин
ServerHttpRequests.cs
Assets/Data/Scripts/Meta/Server/ServerHttpRequests.cs
- HTTP-клиент для всех серверных запросов. Устанавливает JWT в заголовок
Authorization, версию вversion. Поддерживает кэширование, дедупликацию запросов, отмену. При 401 вызываетSessionManager.NotifySessionInvalid().
LoginByDevice(deviceId, callback) → POST /auth/device
LoginBySocial(providerType, token, callback) → POST /auth/social
LinkSocial(request, callback) → POST /auth/linkSocial
RelinkSocial(request, callback) → POST /auth/relinkSocial
UnlinkSocial(type, callback) → POST /auth/unlinkSocial
GetLinkedSocials(callback) → POST /auth/linkedSocials
CheckSocial(request, callback) → POST /auth/checkSocial
LoadPlayerData(callback) → POST /save/load (Save микросервис)
SavePlayerData(version, data, callback) → POST /save/save
ChangeOwner(callback) → POST /save/changeowner
MakeSnapshot(data, callback) → POST /save/snapshot
DeletePlayerData(callback) → POST /save/delete
Примечание:
ClearDeviceдоступен только вServerHttpRequestsEditor(editor-only).
ServerHttpRequestModels.cs
Assets/Data/Scripts/Meta/Server/ServerHttpRequestModels.cs
- Все сериализуемые модели запросов и ответов для HTTP API. Содержит enum-ы
AuthProviderType,LinkSocialResult,SaveStatus,ChosenAccountTypeи все request/response классы.
SaveRequests.cs
Assets/Data/Scripts/Meta/Server/SaveRequests.cs
- Обёртка над
ServerHttpRequestsдля облачных сохранений. ОбрабатываетSaveStatus.NoSessionиSessionMismatchчерезSessionManager.NotifySessionInvalid(). Поддерживает конфликт-резолвер (IConflictResolver), снэпшоты, удаление и смену владельца.
Client — Social
SocialLinkManager.cs
Assets/Data/Scripts/Meta/Social/SocialLinkManager.cs
- Основной API для привязки/отвязки соцсетей с обработкой конфликтов. Синглтон. Кэширует список привязок (60 сек). При
OtherLinkзагружает данные linked-аккаунта и показывает диалог выбора. Отправляет аналитику черезLinkSocialAnalytics.
Link(type, token, callback) → POST /auth/linkSocial
KeepCurrentProgress(type, token, cb) → POST /auth/relinkSocial (Current)
RestoreProgress(type, token, cb) → POST /auth/relinkSocial (Linked)
Unlink(type, callback) → POST /auth/unlinkSocial
RefreshLinkedSocials(callback) → POST /auth/linkedSocials (кэш 60 сек)
IsLinked(type) — проверка привязки из локального кэша
static event Action<AuthProviderType> OnSocialLinked;
static event Action<AuthProviderType> OnSocialUnlinked;
static event Action OnLinkedSocialsRefreshed; // после обновления списка с сервера
SocialLinksComponent.cs
Assets/Data/Scripts/Meta/Social/SocialLinksComponent.cs
- Компонент профиля (наследует
ProfileComponentBase) для хранения состояния привязанных провайдеров. Хранит списокLinkedProvider(Type, Token, ProviderId). Поддерживает отложенное сохранение через параметрsave.
IsLinked(type) — проверка привязки
GetLinkedProvider(type) — данные провайдера
MarkAsLinked(type, token, providerId, save=true) — отметить как привязанный
MarkAsUnlinked(type, save=true) — отметить как отвязанный
GetLinkedProviders() — список всех привязанных
LinkedCount — количество привязанных
ISocialNetwork.cs
Assets/Data/Scripts/Meta/Social/ISocialNetwork.cs
- Интерфейс для SDK социальных сетей. Определяет контракт: инициализация, открытие/закрытие сессии, получение токена и UserId. Наследует
ILoginAuthProvider.
SocialNetworks.cs
Assets/Data/Scripts/Meta/Social/SocialNetworks.cs
- Реестр и менеджер всех зарегистрированных социальных сетей. Синглтон. Позволяет регистрировать, получать по типу и проверять доступность SDK-реализаций.
static SocialNetworks Instance
RegisterSocialNetwork(ISocialNetwork) — регистрация
Get(AuthProviderType type) — получить по типу
IsAvailable(AuthProviderType type) — доступна и инициализирована
OpenSession(AuthProviderType, entryPoint, cb) — открыть сессию
SocialNetworksDefaultRegistrator.cs
Assets/Data/Scripts/Meta/Social/SocialNetworksDefaultRegistrator.cs
- Платформенная регистрация SDK. iOS: Apple + Google + Facebook. Android: Google + Facebook. В Editor/эмуляции подставляет
EmulatedSocialNetwork.
SocialNetworkBase.cs
Assets/Data/Scripts/Meta/Social/SocialNetworkBase.cs
- Абстрактный базовый класс для реализаций
ISocialNetwork. Делегирует вInitCore/OpenSessionCore/CloseSessionCore. Автоматически отправляет аналитику черезLinkSocialAnalytics.
EmulatedSocialStorage.cs
Assets/Data/Scripts/Meta/Social/EmulatedSocialStorage.cs
- Хранилище данных эмулированных соцсетей в
PlayerPrefsпо ключуEmulatedSocial_{playerId}. Поддерживает кэширование и CRUD-операции дляProviderData(UserId, Token).
LinkSocialAnalytics.cs
Assets/Data/Scripts/Meta/Social/LinkSocialAnalytics.cs
- Статический класс аналитики для всех операций привязки соцсетей. Отправляет события в Amplitude через
Analytics.SendEvent().
SendLinkSocial(provider, result, time) — результат привязки
SendLinkSocialVersionMismatch(provider, localVer, linkedVer) — несовпадение версий сейвов
SendLinkConflictShown(provider, currentProfile, linkedProfile) — показан диалог конфликта
SendLinkConflictResolved(provider, keepCurrent, current, linked) — выбор в диалоге конфликта
SendLinkConflictAutoResolved(provider, reason) — автоматическое разрешение
SendRelinkSocial(provider, choice, success, time) — результат relink
SendRestoreProgress(provider, oldPlayerId, newPlayerId) — восстановление прогресса
SendUnlinkSocial(provider, success, message) — отвязка
SendSocialOpenSession(provider, entryPoint, isSuccess, isAuto) — открытие сессии SDK
SendSocialCloseSession(provider) — закрытие сессии SDK
SendSyncError(errorCode, operation, provider?, message?) — ошибка синхронизации
Client — Social/Networks
AppleSocialNetwork.cs
Assets/Data/Scripts/Meta/Social/Networks/AppleSocialNetwork.cs
- Реализация Sign in with Apple (iOS). Использует
AppleAuthManager. Поддерживает тихий логин при инициализации черезGetCredentialState(). Возвращает identity token.
GoogleSocialNetwork.cs
Assets/Data/Scripts/Meta/Social/Networks/GoogleSocialNetwork.cs
- Реализация Google Sign-In (iOS, Android). Использует Google Play Services и Firebase. Поддерживает тихий логин через
SignInSilently(). Возвращает ID token.
FacebookSocialNetwork.cs
Assets/Data/Scripts/Meta/Social/Networks/FacebookSocialNetwork.cs
- Реализация Facebook Login (iOS, Android). Использует Facebook Unity SDK. Запрашивает только
public_profile. Возвращает access token.
EmulatedSocialNetwork.cs
Assets/Data/Scripts/Meta/Social/Networks/EmulatedSocialNetwork.cs
- Эмуляция для Editor и тестов. Показывает popup для ввода UserId. Генерирует токен формата
emulated_{type}_{userId}. Хранит данные вEmulatedSocialStorage.
BlankSocialNetwork.cs
Assets/Data/Scripts/Meta/Social/Networks/BlankSocialNetwork.cs
- Заглушка для недоступных сетей. Всегда
IsInitialized = false,IsSessionOpened = false. Предотвращает null reference errors.
Client — Session
SessionManager.cs
Assets/Data/Scripts/Meta/Server/SessionManager.cs
- Хранение и управление состоянием сессии. Синглтон, доступен через
LoginAuth.SessionиSessionManager.Instance. Извлекает SessionId из JWT через Base64Url-декодирование payload.
public class SessionManager
{
public static SessionManager Instance { get; private set; }
public SessionInfo SessionInfo { get; private set; }
public string SessionId => SessionInfo?.SessionId;
public string DeviceId => SessionInfo?.DeviceId;
public bool HasSession => !string.IsNullOrEmpty(SessionId);
public event Action<bool> OnSessionInvalid; // bool hadSession
public event Action<string> OnSessionKicked; // string reason
public void Init(LoginAuth auth);
public void SetSession(SessionInfo sessionInfo);
public void SetSessionFromToken(string token); // извлекает SessionId из JWT
public void ClearSession();
public void NotifySessionInvalid();
public void NotifySessionKicked(string reason);
}
SessionHandler.cs
Assets/Data/Scripts/Meta/Server/SessionHandler.cs
- Обрабатывает UI-события сессии. При
OnSessionInvalidбез активной сессии — тихий перелогин. При активной сессии илиOnSessionKicked— диалог с локализованным текстом (session_kicked.*) → выход из приложения.
TokenService.cs
Assets/Data/Scripts/Meta/Server/TokenService.cs
- Статический парсер JWT на клиенте. Извлекает PlayerId (
nameidentifierclaim) и SessionId (sidclaim) без внешних библиотек. Manual Base64Url decoding.
public static class TokenService
{
public static TokenInfo Parse(string token);
public static bool HasSession(string token);
public static string GetSessionId(string token);
public static string GetPlayerId(string token);
public static bool IsValidForPlayer(string token, string playerId);
}
SignalRequests.cs
Assets/Data/Scripts/Meta/Server/SignalRequests.cs
- Управление SignalR-соединением. Обрабатывает сигнал
SessionKicked— при совпадении SessionId вызываетSessionManager.NotifySessionKicked(). Поддерживает экспоненциальный backoff при переподключении.
Client — Прочее
PlayerProfileSaver.cs
Assets/Data/Scripts/Meta/PlayerProfile/PlayerProfileSaver.cs
- Сохранение профиля игрока локально и в облако. При кике сессии вызывается с
skipCloudSave: trueдля сохранения только локально.
ApplicationLifecycleHandler.cs
Assets/Data/Scripts/Application/Services/ApplicationLifecycleHandler.cs
- Обработка lifecycle приложения (pause/resume). При pause — локальное сохранение. При resume — переподключение SignalR (сессия не пересоздаётся).
Server — Main
AuthController.cs
Server/Main/Controllers/AuthController.cs
- Все эндпоинты аутентификации и привязки соцсетей. Зависимости:
PlayerContext,IConfiguration,IOAuthService,SessionService.
Device(request) — аутентификация по Device ID + создание сессии в Redis
Social(request) — аутентификация через соцсеть (Google/Apple/Facebook), без сессии
LinkSocial(request) — привязка с автолинком (OtherLink при конфликте)
RelinkSocial(request) — разрешение конфликта:
• Current → переносит одну соцсеть на текущий
• Linked → переносит ВСЕ провайдеры на linked
UnlinkSocial(request) — отвязка соцсети
LinkedSocials() — список привязанных соцсетей (без OAuth токена)
CheckSocial(request) — проверка привязки (требует OAuth токен)
ClearDevice(deviceId) — удаление привязки Device (требует AdminAuth)
Вспомогательные:
GetOrCreatePlayer(type, uid, legacyId) — создание/получение игрока
ValidateSocialToken(type, token) — валидация токена соцсети через IOAuthService
(Google API, Apple OpenID, Facebook debug_token или /me)
GenerateToken(playerId) — генерация JWT без сессии (для соцсетей)
GenerateTokenWithSession(playerId, sid, scat) — генерация JWT с session claims (для Device)
ValidateAuthHeader(authorization) — извлечение PlayerId из JWT
NotifySessionKicked(playerId, kickedSid, reason) — отправка SignalR уведомления о кике
SessionService.cs
Server/Main/Services/SessionService.cs
- Управление сессиями в Redis. Создаёт новые сессии (всегда перезаписывает), определяет нужен ли кик (другой DeviceId). Ключ:
session:{playerId}, поля:sid,did,cat.
CreateSession(playerId, deviceId) → CreateSessionResult
GetSessionInfo(playerId) → SessionInfo?
OAuthService.cs
Server/Main/Services/OAuthService.cs
- Продакшн-валидация OAuth-токенов. Google: через
Google.Apis.Auth. Apple: JWT-верификация через OpenID Connect. Facebook: debug_token endpoint или/meendpoint.
IOAuthService.cs
Server/Main/Services/IOAuthService.cs
- Интерфейс валидации OAuth-токенов.
Task<string?> ValidateGoogleToken(string token)
Task<string?> ValidateAppleToken(string token)
Task<string?> ValidateFacebookToken(string token)
Task<string?> ValidateFacebookTokenViaMe(string token)
| Реализация | Назначение |
|---|---|
OAuthService |
Продакшн — валидация через внешние API |
EmulatedOAuthService |
Эмуляция — принимает токены формата emulated_* |
DummyOAuthService |
Заглушка — всегда возвращает null |
JwtAuthMiddleware.cs
Server/Main/Middleware/JwtAuthMiddleware.cs
- Извлекает JWT из
Authorization: Bearer {token}, валидирует черезJwtService.ValidateAndGetInfo(), устанавливаетHttpContext.Items["TokenInfo"]для контроллеров.
AdminAuthAttribute.cs
Server/Main/Middleware/AdminAuthAttribute.cs
- Атрибут
[AdminAuth]для защиты dev-эндпоинтов (например,/auth/cleardevice). Проверяет заголовокadmin-token.
AuthPlayer.cs
Server/Main/Model/DB/AuthPlayer.cs
- EF Core модель игрока. PlayerId — 16-character hex ID. Содержит навигационное свойство
AuthProviders.
AuthProvider.cs
Server/Main/Model/DB/AuthProvider.cs
- EF Core модель провайдера аутентификации. Связывает игрока с Device ID или соцсетью. Поле
PreviousPlayerIdиспользуется для отслеживания Relink.
Server — Save
SaveController.cs
Server/Save/Controllers/SaveController.cs
- Контроллер сохранений. Проверяет сессию для клиентов >= 3.0 (
IsSessionRequired). Извлекает SessionId из JWT claims. Поддерживает save, load, snapshot, delete, changeowner.
JwtAuthMiddleware.cs
Server/Save/Middleware/JwtAuthMiddleware.cs
- JWT middleware для Save-микросервиса. Аналогичен Main-версии.
Server — Shared
JwtService.cs
Server/Shared/JwtService.cs
- Генерация и валидация JWT-токенов. Issuer/Audience: "Zombusters". Алгоритм: HmacSha256. Токены не имеют срока действия.
GenerateToken(playerId, secret) — JWT без сессии
GenerateToken(playerId, sessionId, sessionCreatedAt, secret) — JWT с session claims
ValidateToken(token, secret) → string? — валидация, возвращает PlayerId
ValidateAndGetInfo(token, secret) → TokenInfo? — валидация + полная информация
GeneratePlayerId() — уникальный ID (16 hex chars, криптографический random)