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