✨ Device alternative for related device (like watch) to connect websocket
This commit is contained in:
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user