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