using System; using System.Threading.Tasks; using Vanguard.Api; using Vanguard.Api.Contracts; using Vanguard.State; namespace Vanguard.Auth { /// /// Сессия игрока: login/register, восстановление по refresh-токену, /// обновление профиля и выход. Токены не покидают клиент. /// public sealed class AuthService { private readonly ApiClient _api; private readonly TokenStore _tokens; private readonly PlayerState _player; public AuthService(ApiClient api, TokenStore tokens, PlayerState player) { _api = api; _tokens = tokens; _player = player; } public bool IsAuthenticated => _tokens.HasRefreshToken; public async Task LoginAsync(string userName, string password) { var response = await _api.PostAsync( "/api/auth/login", new LoginRequest { UserName = userName, Password = password }, authenticated: false); SaveSession(response); await TryRefreshMeAsync(); return response; } public async Task RegisterAsync(string userName, string password) { var response = await _api.PostAsync( "/api/auth/register", new RegisterRequest { UserName = userName, Password = password }, authenticated: false); SaveSession(response); await TryRefreshMeAsync(); return response; } /// Восстановить сессию при старте. false — нужен логин. public async Task TryRestoreAsync() { if (!_tokens.HasRefreshToken || _tokens.RefreshExpiresAt <= DateTimeOffset.UtcNow) return false; try { await RefreshAsync(); await TryRefreshMeAsync(); return true; } catch (ApiException exception) when (exception.StatusCode == 401) { ClearSession(); return false; } catch (Exception) { // Сервер временно недоступен: токены не трогаем, покажем логин. return false; } } public async Task RefreshAsync() { if (!_tokens.HasRefreshToken) throw new ApiException(401, "auth.no-refresh", "Нет refresh-токена."); var response = await _api.PostAsync( "/api/auth/refresh", new RefreshRequest { RefreshToken = _tokens.RefreshToken }, authenticated: false); SaveSession(response); return response; } public async Task RefreshMeAsync() { if (_tokens.IsAccessExpired && _tokens.HasRefreshToken) await RefreshAsync(); try { var me = await _api.GetAsync("/api/auth/me"); _player.Apply(me); return me; } catch (ApiException exception) when (exception.StatusCode == 401 && _tokens.HasRefreshToken) { await RefreshAsync(); var me = await _api.GetAsync("/api/auth/me"); _player.Apply(me); return me; } } public async Task LogoutAsync() { try { if (_tokens.HasRefreshToken) { await _api.PostAsync( "/api/auth/logout", new RefreshRequest { RefreshToken = _tokens.RefreshToken }, authenticated: false); } } catch (Exception) { // Выход не должен зависеть от доступности сервера. } finally { ClearSession(); } } private async Task TryRefreshMeAsync() { try { await RefreshMeAsync(); } catch (Exception) { // Логин уже состоялся; профиль можно обновить на мета-экране. } } private void SaveSession(AuthResponse response) { _tokens.Save( response.AccessToken, response.RefreshToken, response.AccessExpiresAt, response.RefreshExpiresAt); _player.Apply(response); } private void ClearSession() { _tokens.Clear(); _player.Clear(); } } }