Changed logic for reconnect to not rely on serverstate API.

Changed websocket to send current state on initial connect.
This commit is contained in:
Kenneth Skovhede
2024-07-11 16:24:22 +02:00
parent 0ae9990f18
commit 94d6ab76ee
5 changed files with 57 additions and 49 deletions
@@ -82,6 +82,10 @@ backupApp.service('AppService', function ($http, $cookies, $q, $cookies, DialogS
return deferred.promise;
};
this.clearAccessToken = function () {
self.access_token = null;
self.access_token_promise = null;
}
// Returns a promise that resolves to the access token
this.getAccessToken = function () {
@@ -252,23 +252,15 @@ backupApp.service('ServerStatus', function ($rootScope, $timeout, AppService, Ap
function handleConnectionError(response) {
state.failedConnectionAttempts++;
var errorMessage = AppService.responseErrorMessage(response);
// First failure, we ignore
if (state.connectionState == 'connected' && state.failedConnectionAttempts == 1) {
// Try again
countdownForReconnect(function () {
updateServerState();
});
updateServerState();
} else if (response.status == 401) {
// Change state to connected to hide the connecting message, which is on top of the login message from the AppService
state.connectionState = 'unauthorized';
// Notify
$rootScope.$broadcast('serverstatechanged');
} else {
state.connectionState = 'disconnected';
// Notify
$rootScope.$broadcast('serverstatechanged');
countdownForReconnect(function () {
updateServerState();
@@ -282,37 +274,35 @@ backupApp.service('ServerStatus', function ($rootScope, $timeout, AppService, Ap
$rootScope.$broadcast('serverstatechanged');
}
const url = window.location.origin + '/api/v1/serverstate';
AppService.get(url, {timeout: state.lastEventId > 0 ? longpolltime : 5000}).then(
handleServerState, handleConnectionError
);
self.reconnect();
};
updateServerState();
this.reconnect = function () {
var reconnect_websocket = function () {
window.clearInterval(longPollRetryTimer);
const w = new WebSocket('ws://' + window.location.host + '/notifications?token=' + AppService.access_token)
w.addEventListener("open", (event) => {
state.connectionState = 'connected';
$rootScope.$broadcast('serverstatechanged.connectionState', state.connectionState);
$rootScope.$broadcast('serverstatechanged');
});
w.addEventListener("message", (event) => {
const status = JSON.parse(event.data);
handleServerState({data: status});
});
w.addEventListener("close", (event) => {
if (event.code === 4401) {
state.connectionState = 'unauthorized';
$rootScope.$broadcast('serverstatechanged');
}
window.websocket = null;
if (event.code === 4401)
AppService.clearAccessToken();
handleConnectionError({status: event.code});
});
return w;
};
AppService.access_token_promise.then(() => {
let websocket = this.reconnect()
window.websocket = websocket;
});
this.reconnect = function () {
AppService.getAccessToken().then(() => {
let websocket = reconnect_websocket()
window.websocket = websocket;
}, resp => {
handleConnectionError(resp);
});
}
this.reconnect();
});
@@ -4,8 +4,7 @@ namespace Duplicati.WebserverCore.Abstractions.Notifications;
public interface IWebsocketAccessor
{
void AddConnection(WebSocket newConnection);
WebSocket[] OpenConnections { get; }
Task AddConnection(WebSocket newConnection);
Task Send<T>(T data);
Task HandleClientMessage(string message);
}
@@ -1,8 +1,6 @@
using System.Net.WebSockets;
using System.Text;
using Duplicati.WebserverCore.Abstractions;
using Duplicati.WebserverCore.Abstractions.Notifications;
using Duplicati.WebserverCore.Exceptions;
namespace Duplicati.WebserverCore.Middlewares;
@@ -37,8 +35,9 @@ public static class WebsocketExtensions
if (context.WebSockets.IsWebSocketRequest)
{
using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
websocketAccessor.AddConnection(webSocket);
var initial = websocketAccessor.AddConnection(webSocket);
await HandleClientData(webSocket, websocketAccessor);
await initial;
}
else
{
@@ -9,34 +9,41 @@ namespace Duplicati.WebserverCore.Notifications;
public class WebsocketAccessor : IWebsocketAccessor
{
private object _lock = new object();
private List<WebSocket> _connections = new();
private readonly JsonSerializerSettings m_jsonSettings;
private readonly IServiceProvider m_serviceProvider;
public WebsocketAccessor(JsonSerializerSettings jsonSettings, EventPollNotify eventPollNotify, IServiceProvider serviceProvider)
{
m_jsonSettings = jsonSettings;
m_serviceProvider = serviceProvider;
eventPollNotify.NewEvent += async (_, _) =>
{
using var scope = serviceProvider.CreateScope();
var statusService = scope.ServiceProvider.GetRequiredService<IStatusService>();
await Send(statusService.GetStatus());
await Send(StatusService.GetStatus());
};
}
public void AddConnection(WebSocket newConnection)
private IStatusService StatusService => m_serviceProvider.GetService<IStatusService>() ?? throw new Exception("StatusService not found");
public async Task AddConnection(WebSocket newConnection)
{
_connections.Add(newConnection);
await SendInitialStatus(newConnection);
lock (_lock)
_connections.Add(newConnection);
ClearClosed();
}
private async Task SendInitialStatus(WebSocket connection)
=> await Send(StatusService.GetStatus(), [connection]);
private void ClearClosed()
{
_connections = _connections.Where(c => c.State == WebSocketState.Open).ToList();
lock (_lock)
_connections = _connections.Where(c => c.State == WebSocketState.Open).ToList();
}
public WebSocket[] OpenConnections => Connections.ToArray();
private IEnumerable<WebSocket> Connections
{
get
@@ -46,19 +53,28 @@ public class WebsocketAccessor : IWebsocketAccessor
}
}
public async Task Send<T>(T data)
private Task Send<T>(T data, IEnumerable<WebSocket> connections)
{
var json = JsonConvert.SerializeObject(data, m_jsonSettings);
var bytes = json.GetBytes();
foreach (var webSocket in Connections)
{
await webSocket.SendAsync(bytes, WebSocketMessageType.Text, true, CancellationToken.None);
}
lock (_lock)
connections = connections.ToList();
return Task.WhenAll(
connections
.Where(c => c.State == WebSocketState.Open)
.Select(c => c.SendAsync(bytes, WebSocketMessageType.Text, true, CancellationToken.None))
);
}
public async Task HandleClientMessage(string message)
public Task Send<T>(T data)
=> Send(data, Connections);
public Task HandleClientMessage(string message)
{
//TODO: handle client message
return Task.CompletedTask;
}
}