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-токена.");
try
{
var response = await _api.PostAsync(
"/api/auth/refresh",
new RefreshRequest { RefreshToken = _tokens.RefreshToken },
authenticated: false);
SaveSession(response);
return response;
}
catch (ApiException exception) when (exception.StatusCode == 401)
{
ClearSession();
throw;
}
}
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();
}
}
}