svrooij / teams-monitor

Send your Teams Status to any webhook in realtime
https://www.nuget.org/packages/SvRooij.TeamsMonitor/
GNU General Public License v3.0
65 stars 4 forks source link

Feature request: access to WebSocket state and (dis)connection events #11

Open matthijs110 opened 5 months ago

matthijs110 commented 5 months ago

I am using the TeamsMonitor.Core NuGet package to connect with Microsoft Teams. I want to implement a retry mechanism, just like Microsoft does with the Stream Deck plugin. This way, a new connection attempt is made when the connection is closed, with an incrementing timeout in between.

I'd love to have access to the WebSocket instance to know if the WebSocket connection is (still) open. Are you willing to expose the ClientWebSocket.State and perhaps implement addition event listeners like Connected and Disconnected as well? :)

Really love this package, as this takes away a lot of research and reverse engineering! I did both, but this package still makes it a lot easier!

svrooij commented 5 months ago

I'm all up for adding code like this:

public WebSocketState CurrentState => webSocket.State;
public event EventHandler<State> StateChanged;

Not sure how we would go about adding a retry mechanism, but that would be cool.

matthijs110 commented 5 months ago

Yeah, looks good! I was thinking in seperate events for Connecting and Disconncted though, but I guess your suggestion may work as well :)

public event EventHandler Connected;
public event EventHandler Disconnected;

And perhaps you may as well add a TokenRefreshed event in case you want to handle this event in own solutions.


The retry mechanism I am implementing simply does exactly the same as the Stream Deck solution. What you could do, is the following (quick and dirty proposal):

private const int MinTimeoutMs = 250; // 1/4 seconds
private const int MaxTimeoutMs = 60_000; // 60 seconds
private int NextReconnectTimeoutMs = 0;

public async Task ConnectAsync(bool blocking, bool reconnectOnClose, CancellationToken cancellationToken)
{
    try {
        // Attempt first connection
        await webSocket.ConnectAsync(options.SocketUri, cancellationToken);

        // Continue on succes
        if (blocking)
            await ReadUntilCancelled(cancellationToken);
        else
            backgroundTask = Task.Run(() => ReadUntilCancelled(cancellationToken), cancellationToken);
    }
    catch (WebSocketException ex) {
        if (!IsConnected && reconnectOnClose && !cancellationToken.IsCancellationRequested)
        {
            var timeout = NextReconnectTimeoutMs;

            // Get the (next) timeout up until 60 seconds max.
            NextReconnectTimeoutMs = Math.Min(MaxTimeoutMs, timeout * 2);

            Console.WriteLine($"Reconnection attempt in {timeout / 1000} seconds ({timeout} ms)...");

            // Execute timeout
            await Task.Delay(timeout, cancellationToken);

            // Do another attempt.
            await ConnectAsync(cancellationToken, reconnectOnClose);
        }
    }
}

You may as well introduce a ConnectWithRetryAsync() method executing the original ConnectAsync with the retry mechanism around it.