Device alternative for related device (like watch) to connect websocket

This commit is contained in:
2025-10-30 21:26:58 +08:00
parent 8f1047ff5d
commit ab23f87a66

View File

@@ -17,42 +17,52 @@ public class WebSocketController(
INatsConnection nats
) : ControllerBase
{
private static readonly List<string> AllowedDeviceAlternative = ["watch"];
[Route("/ws")]
[Authorize]
[SwaggerIgnore]
public async Task TheGateway()
public async Task<ActionResult> TheGateway([FromQuery] string? deviceAlt)
{
if (string.IsNullOrWhiteSpace(deviceAlt))
deviceAlt = null;
if (deviceAlt is not null && !AllowedDeviceAlternative.Contains(deviceAlt))
return BadRequest("Unsupported device alternative: " + deviceAlt);
HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);
HttpContext.Items.TryGetValue("CurrentSession", out var currentSessionValue);
if (currentUserValue is not Account currentUser ||
currentSessionValue is not AuthSession currentSession)
if (
currentUserValue is not Account currentUser
|| currentSessionValue is not AuthSession currentSession
)
{
HttpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
return;
return Unauthorized();
}
var accountId = Guid.Parse(currentUser.Id!);
var deviceId = currentSession.Challenge?.DeviceId ?? Guid.NewGuid().ToString();
if (string.IsNullOrEmpty(deviceId))
{
HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
return;
}
return BadRequest("Unable to get device ID from session.");
if (deviceAlt is not null)
deviceId = $"{deviceId}+{deviceAlt}";
var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(new WebSocketAcceptContext
{ KeepAliveInterval = TimeSpan.FromSeconds(60) });
var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(
new WebSocketAcceptContext { KeepAliveInterval = TimeSpan.FromSeconds(60) }
);
var cts = new CancellationTokenSource();
var connectionKey = (accountId, deviceId);
if (!ws.TryAdd(connectionKey, webSocket, cts))
{
await webSocket.SendAsync(
new ArraySegment<byte>(new WebSocketPacket
{
Type = "error.dupe",
ErrorMessage = "Too many connections from the same device and account."
}.ToBytes()),
new ArraySegment<byte>(
new WebSocketPacket
{
Type = "error.dupe",
ErrorMessage = "Too many connections from the same device and account.",
}.ToBytes()
),
WebSocketMessageType.Binary,
true,
CancellationToken.None
@@ -62,21 +72,26 @@ public class WebSocketController(
"Too many connections from the same device and account.",
CancellationToken.None
);
return;
return new EmptyResult();
}
logger.LogDebug(
$"Connection established with user @{currentUser.Name}#{currentUser.Id} and device #{deviceId}");
$"Connection established with user @{currentUser.Name}#{currentUser.Id} and device #{deviceId}"
);
// Broadcast WebSocket connected event
await nats.PublishAsync(
WebSocketConnectedEvent.Type,
GrpcTypeHelper.ConvertObjectToByteString(new WebSocketConnectedEvent
{
AccountId = accountId,
DeviceId = deviceId,
IsOffline = false
}).ToByteArray(),
GrpcTypeHelper
.ConvertObjectToByteString(
new WebSocketConnectedEvent
{
AccountId = accountId,
DeviceId = deviceId,
IsOffline = false,
}
)
.ToByteArray(),
cancellationToken: cts.Token
);
@@ -84,7 +99,11 @@ public class WebSocketController(
{
await _ConnectionEventLoop(deviceId, currentUser, webSocket, cts.Token);
}
catch (WebSocketException ex) when (ex.Message.Contains("The remote party closed the WebSocket connection without completing the close handshake"))
catch (WebSocketException ex)
when (ex.Message.Contains(
"The remote party closed the WebSocket connection without completing the close handshake"
)
)
{
logger.LogDebug(
"WebSocket disconnected with user @{UserName}#{UserId} and device #{DeviceId} - client closed connection without proper handshake",
@@ -95,7 +114,8 @@ public class WebSocketController(
}
catch (Exception ex)
{
logger.LogError(ex,
logger.LogError(
ex,
"WebSocket disconnected with user @{UserName}#{UserId} and device #{DeviceId} unexpectedly",
currentUser.Name,
currentUser.Id,
@@ -109,12 +129,16 @@ public class WebSocketController(
// Broadcast WebSocket disconnected event
await nats.PublishAsync(
WebSocketDisconnectedEvent.Type,
GrpcTypeHelper.ConvertObjectToByteString(new WebSocketDisconnectedEvent
{
AccountId = accountId,
DeviceId = deviceId,
IsOffline = !WebSocketService.GetAccountIsConnected(accountId)
}).ToByteArray(),
GrpcTypeHelper
.ConvertObjectToByteString(
new WebSocketDisconnectedEvent
{
AccountId = accountId,
DeviceId = deviceId,
IsOffline = !WebSocketService.GetAccountIsConnected(accountId),
}
)
.ToByteArray(),
cancellationToken: cts.Token
);
@@ -122,6 +146,8 @@ public class WebSocketController(
$"Connection disconnected with user @{currentUser.Name}#{currentUser.Id} and device #{deviceId}"
);
}
return new EmptyResult();
}
private async Task _ConnectionEventLoop(