corp-app/CORP-MAUI/Services/WebSocketService.cs

297 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Diagnostics;
using System.Net.WebSockets;
using System.Text;
using CORP_MAUI.Models;
using Newtonsoft.Json;
namespace CORP_MAUI.Services;
public class WebSocketService
{
private ClientWebSocket? _webSocket;
// Возможные варианты WebSocket URL (будем пробовать по порядку)
private static readonly string[] WebSocketUrls =
{
"wss://corp.robotail.pro/ws",
"ws://corp.robotail.pro/ws",
"wss://corp.robotail.pro/ws/",
"ws://corp.robotail.pro/ws/",
"wss://corp.robotail.pro/api/ws",
"wss://corp.robotail.pro/api/v1/ws/chat/"
};
private string? _connectedUrl;
private CancellationTokenSource? _cancellationTokenSource;
private bool _isConnected;
public event EventHandler<Message>? MessageReceived;
public event EventHandler<User>? UserStatusChanged;
public event EventHandler<bool>? ConnectionStatusChanged;
public bool IsConnected => _isConnected && _webSocket?.State == WebSocketState.Open;
public async Task ConnectAsync(string authToken)
{
Debug.WriteLine("\n╔════════════════════════════════════════════════════════════");
Debug.WriteLine("║ [WebSocket] CONNECTION ATTEMPT");
Debug.WriteLine("╚════════════════════════════════════════════════════════════");
Debug.WriteLine($"[WebSocket] Has Token: {!string.IsNullOrEmpty(authToken)}");
if (string.IsNullOrEmpty(authToken))
{
Debug.WriteLine("[WebSocket] ❌ ERROR: No authentication token provided!");
throw new ArgumentException("Authentication token is required", nameof(authToken));
}
if (_webSocket is { State: WebSocketState.Open })
{
Debug.WriteLine("[WebSocket] Closing existing connection...");
await DisconnectAsync();
}
// Пробуем подключиться к разным URL по очереди
Exception? lastException = null;
foreach (var url in WebSocketUrls)
{
try
{
Debug.WriteLine($"\n[WebSocket] >>> Trying: {url}");
_webSocket = new ClientWebSocket();
// 🔑 Устанавливаем Authorization заголовок
Debug.WriteLine($"[WebSocket] 🔑 Setting Authorization header...");
Debug.WriteLine($"[WebSocket] Token (first 30 chars): {authToken.Substring(0, Math.Min(30, authToken.Length))}...");
Debug.WriteLine($"[WebSocket] Token length: {authToken.Length}");
_webSocket.Options.SetRequestHeader("Authorization", $"Bearer {authToken}");
Debug.WriteLine($"[WebSocket] ✅ Authorization header set: Bearer [token]");
_cancellationTokenSource = new CancellationTokenSource();
var uri = new Uri(url);
Debug.WriteLine($"[WebSocket] Connecting to: {uri}");
await _webSocket.ConnectAsync(uri, _cancellationTokenSource.Token);
// Если дошли сюда - подключение успешно!
_isConnected = true;
_connectedUrl = url;
ConnectionStatusChanged?.Invoke(this, true);
Debug.WriteLine($"[WebSocket] ✅ Connected successfully!");
Debug.WriteLine($"[WebSocket] URL: {url}");
Debug.WriteLine($"[WebSocket] State: {_webSocket.State}\n");
// Запускаем прослушивание сообщений
_ = Task.Run(async () => await ReceiveMessagesAsync());
return; // Успешно подключились - выходим
}
catch (WebSocketException wsEx)
{
Debug.WriteLine($"[WebSocket] ❌ Failed to connect to: {url}");
Debug.WriteLine($" WebSocketError: {wsEx.WebSocketErrorCode}");
Debug.WriteLine($" Message: {wsEx.Message}");
Debug.WriteLine($" InnerException: {wsEx.InnerException?.Message}");
lastException = wsEx;
// Закрываем и очищаем
_webSocket?.Dispose();
_webSocket = null;
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
}
catch (Exception ex)
{
Debug.WriteLine($"[WebSocket] ❌ Failed to connect to: {url}");
Debug.WriteLine($" Exception: {ex.GetType().Name}");
Debug.WriteLine($" Message: {ex.Message}");
lastException = ex;
// Закрываем и очищаем
_webSocket?.Dispose();
_webSocket = null;
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
}
}
// Если дошли сюда - ни один URL не сработал
_isConnected = false;
ConnectionStatusChanged?.Invoke(this, false);
Debug.WriteLine($"\n[WebSocket] ❌ ALL CONNECTION ATTEMPTS FAILED!");
Debug.WriteLine($"[WebSocket] Tried {WebSocketUrls.Length} different URLs");
Debug.WriteLine($"[WebSocket] Last error: {lastException?.Message}\n");
throw new Exception($"Не удалось подключиться к WebSocket. Попробовано {WebSocketUrls.Length} вариантов URL.", lastException);
}
public async Task DisconnectAsync()
{
try
{
if (_webSocket is { State: WebSocketState.Open })
{
await _cancellationTokenSource?.CancelAsync()!;
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);
}
_isConnected = false;
ConnectionStatusChanged?.Invoke(this, false);
}
catch (Exception ex)
{
Debug.WriteLine($"Ошибка отключения WebSocket: {ex.Message}");
}
finally
{
_webSocket?.Dispose();
_cancellationTokenSource?.Dispose();
}
}
public async Task SendMessageAsync(Message message)
{
if (!IsConnected)
{
throw new InvalidOperationException("WebSocket не подключен");
}
try
{
var json = JsonConvert.SerializeObject(new
{
type = "message",
data = message
});
var bytes = Encoding.UTF8.GetBytes(json);
var buffer = new ArraySegment<byte>(bytes);
if (_webSocket != null && _cancellationTokenSource != null)
{
await _webSocket.SendAsync(buffer, WebSocketMessageType.Text, true, _cancellationTokenSource.Token);
}
}
catch (Exception ex)
{
Debug.WriteLine($"Ошибка отправки сообщения через WebSocket: {ex.Message}");
throw;
}
}
public async Task SendTypingIndicatorAsync(int recipientId, bool isTyping)
{
if (!IsConnected) return;
try
{
var json = JsonConvert.SerializeObject(new
{
type = "typing",
data = new { recipientId, isTyping }
});
var bytes = Encoding.UTF8.GetBytes(json);
var buffer = new ArraySegment<byte>(bytes);
if (_webSocket != null && _cancellationTokenSource != null)
{
await _webSocket.SendAsync(buffer, WebSocketMessageType.Text, true, _cancellationTokenSource.Token);
}
}
catch (Exception ex)
{
Debug.WriteLine($"Ошибка отправки индикатора набора: {ex.Message}");
}
}
private async Task ReceiveMessagesAsync()
{
var buffer = new byte[8192];
try
{
while (_webSocket?.State == WebSocketState.Open && _cancellationTokenSource is { Token.IsCancellationRequested: false })
{
var result = await _webSocket.ReceiveAsync(
new ArraySegment<byte>(buffer),
_cancellationTokenSource.Token);
if (result.MessageType == WebSocketMessageType.Close)
{
await _webSocket.CloseAsync(
WebSocketCloseStatus.NormalClosure,
"Closed by server",
CancellationToken.None);
_isConnected = false;
ConnectionStatusChanged?.Invoke(this, false);
break;
}
var json = Encoding.UTF8.GetString(buffer, 0, result.Count);
await ProcessMessageAsync(json);
}
}
catch (OperationCanceledException)
{
// Нормальное завершение
}
catch (Exception ex)
{
Debug.WriteLine($"Ошибка получения сообщений WebSocket: {ex.Message}");
_isConnected = false;
ConnectionStatusChanged?.Invoke(this, false);
}
}
private async Task ProcessMessageAsync(string json)
{
try
{
var wrapper = JsonConvert.DeserializeObject<WebSocketMessage>(json);
await MainThread.InvokeOnMainThreadAsync(() =>
{
switch (wrapper?.Type)
{
case "message":
Message? message = JsonConvert.DeserializeObject<Message>(wrapper.Data.ToString() ?? string.Empty);
if (message != null) MessageReceived?.Invoke(this, message);
break;
case "user_status":
var user = JsonConvert.DeserializeObject<User>(wrapper.Data.ToString() ?? string.Empty);
if (user != null) UserStatusChanged?.Invoke(this, user);
break;
case "typing":
// Обработка индикатора набора
break;
default:
Debug.WriteLine($"Неизвестный тип сообщения: {wrapper?.Type ?? "null"}");
break;
}
});
}
catch (Exception ex)
{
Debug.WriteLine($"Ошибка обработки сообщения: {ex.Message}");
}
}
private class WebSocketMessage
{
[JsonProperty("type")]
public string Type { get; set; } = string.Empty;
[JsonProperty("data")]
public object Data { get; set; } = new();
}
}