feat: enable subscription logic for messengers

In order to help control traffic over the websocket, a subscription feature has been added:
* A config option, `enableMessengerSubscriptions` has been added
* When true, the MessengerBase class will assume that any message sent using the `PostStatusMessage` that has a valid client ID wants to send any subsequent unsolicited updates to that same client
* The client's ID will be added a list of subscribed client IDs
* Any subsequent messages sent using the `PostStatusMessage` methods that have a null clientId will ONLY be sent to subscribed clients
* When a client disconnects, it will be removed from the list of subscribed clients

This should cut down drastically on the traffic to the UI, especially when combined with requesting partial status updates from a device rather than the entire state.
This commit is contained in:
Andrew Welker
2025-09-26 21:31:54 -05:00
parent 087d0a1149
commit bb694b4200
6 changed files with 239 additions and 9 deletions

View File

@@ -4,6 +4,8 @@ using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using Crestron.SimplSharp;
using Crestron.SimplSharp.WebScripting;
@@ -35,6 +37,10 @@ namespace PepperDash.Essentials.WebSocketServer
private readonly string appConfigFileName = "_config.local.json";
private readonly string appConfigCsFileName = "_config.cs.json";
private const string certificateName = "selfCres";
private const string certificatePassword = "cres12345";
/// <summary>
/// Where the key is the join token and the value is the room key
/// </summary>
@@ -260,7 +266,41 @@ namespace PepperDash.Essentials.WebSocketServer
_server.OnPost += Server_OnPost;
}
_server.Log.Output = (data, level) => this.LogInformation("WebSocket Server Log [{level}]: {data}", level, data);
if (_parent.Config.DirectServer.Secure)
{
this.LogInformation("Adding SSL Configuration to server");
_server.SslConfiguration = new ServerSslConfiguration(new X509Certificate2($"\\user\\{certificateName}.pfx", certificatePassword))
{
ClientCertificateRequired = false,
CheckCertificateRevocation = false,
EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11
};
}
_server.Log.Output = (data, message) =>
{
switch (data.Level)
{
case LogLevel.Trace:
this.LogVerbose(data.Message);
break;
case LogLevel.Debug:
this.LogDebug(data.Message);
break;
case LogLevel.Info:
this.LogInformation(data.Message);
break;
case LogLevel.Warn:
this.LogWarning(data.Message);
break;
case LogLevel.Error:
this.LogError(data.Message);
break;
case LogLevel.Fatal:
this.LogFatal(data.Message);
break;
}
};
CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler;

View File

@@ -22,14 +22,29 @@ namespace PepperDash.Essentials.WebSocketServer
/// <inheritdoc />
public string Key { get; private set; }
/// <summary>
/// Gets or sets the mobile control system controller that handles this client's messages
/// </summary>
public MobileControlSystemController Controller { get; set; }
/// <summary>
/// Gets or sets the room key that this client is associated with
/// </summary>
public string RoomKey { get; set; }
/// <summary>
/// The unique identifier for this client instance
/// </summary>
private string _clientId;
/// <summary>
/// The timestamp when this client connection was established
/// </summary>
private DateTime _connectionTime;
/// <summary>
/// Gets the duration that this client has been connected. Returns zero if not currently connected.
/// </summary>
public TimeSpan ConnectedDuration
{
get
@@ -45,6 +60,10 @@ namespace PepperDash.Essentials.WebSocketServer
}
}
/// <summary>
/// Initializes a new instance of the UiClient class with the specified key
/// </summary>
/// <param name="key">The unique key to identify this client</param>
public UiClient(string key)
{
Key = key;
@@ -99,11 +118,21 @@ namespace PepperDash.Essentials.WebSocketServer
// TODO: Future: Check token to see if there's already an open session using that token and reject/close the session
}
/// <summary>
/// Handles the UserCodeChanged event from a room bridge and sends the updated user code to the client
/// </summary>
/// <param name="sender">The room bridge that raised the event</param>
/// <param name="e">Event arguments</param>
private void Bridge_UserCodeChanged(object sender, EventArgs e)
{
SendUserCodeToClient((MobileControlEssentialsRoomBridge)sender, _clientId);
}
/// <summary>
/// Sends the current user code and QR code URL to the specified client
/// </summary>
/// <param name="bridge">The room bridge containing the user code information</param>
/// <param name="clientId">The ID of the client to send the information to</param>
private void SendUserCodeToClient(MobileControlBridgeBase bridge, string clientId)
{
var content = new
@@ -140,6 +169,16 @@ namespace PepperDash.Essentials.WebSocketServer
base.OnClose(e);
this.LogInformation("WebSocket UiClient Closing: {code} reason: {reason}", e.Code, e.Reason);
foreach (var messenger in Controller.Messengers)
{
messenger.Value.UnsubscribeClient(_clientId);
}
foreach (var messenger in Controller.DefaultMessengers)
{
messenger.Value.UnsubscribeClient(_clientId);
}
}
/// <inheritdoc />