fix: log errors and disconnects for UI Clients

This commit is contained in:
Andrew Welker
2025-09-25 08:36:24 -05:00
parent 06341b14f3
commit fd70377c7f
8 changed files with 253 additions and 240 deletions

View File

@@ -0,0 +1,28 @@
using System.Collections.Generic;
using Newtonsoft.Json;
using PepperDash.Core;
/// <summary>
/// Represents info about a device including supproted interfaces
/// </summary>
public class DeviceInterfaceInfo : IKeyName
{
/// <summary>
/// Gets or sets the Key
/// </summary>
[JsonProperty("key")]
public string Key { get; set; }
/// <summary>
/// Gets or sets the Name
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }
/// <summary>
/// Gets or sets the Interfaces
/// </summary>
[JsonProperty("interfaces")]
public List<string> Interfaces { get; set; }
}

View File

@@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace PepperDash.Essentials.WebSocketServer
{
/// <summary>
/// Represents a JoinResponse
/// </summary>
public class JoinResponse
{
/// <summary>
/// Gets or sets the ClientId
/// </summary>
[JsonProperty("clientId")]
public string ClientId { get; set; }
[JsonProperty("roomKey")]
public string RoomKey { get; set; }
[JsonProperty("systemUUid")]
public string SystemUuid { get; set; }
/// <summary>
/// Gets or sets the RoomUuid
/// </summary>
[JsonProperty("roomUUid")]
public string RoomUuid { get; set; }
/// <summary>
/// Gets or sets the Config
/// </summary>
[JsonProperty("config")]
public object Config { get; set; }
/// <summary>
/// Gets or sets the CodeExpires
/// </summary>
[JsonProperty("codeExpires")]
public DateTime CodeExpires { get; set; }
/// <summary>
/// Gets or sets the UserCode
/// </summary>
[JsonProperty("userCode")]
public string UserCode { get; set; }
/// <summary>
/// Gets or sets the UserAppUrl
/// </summary>
[JsonProperty("userAppUrl")]
public string UserAppUrl { get; set; }
/// <summary>
/// Gets or sets the EnableDebug
/// </summary>
[JsonProperty("enableDebug")]
public bool EnableDebug { get; set; }
///
public Dictionary<string, DeviceInterfaceInfo> DeviceInterfaceSupport { get; set; }
}
}

View File

@@ -0,0 +1,24 @@
namespace PepperDash.Essentials.WebSocketServer
{
/// <summary>
/// Represents a JoinToken
/// </summary>
public class JoinToken
{
/// <summary>
/// Gets or sets the Code
/// </summary>
public string Code { get; set; }
public string RoomKey { get; set; }
public string Uuid { get; set; }
public string TouchpanelKey { get; set; } = "";
/// <summary>
/// Gets or sets the Token
/// </summary>
public string Token { get; set; } = null;
}
}

View File

@@ -155,7 +155,7 @@ namespace PepperDash.Essentials.WebSocketServer
{ {
try try
{ {
Debug.LogMessage(LogEventLevel.Information, "Automatically forwarding port {0} to CS LAN", Port); this.LogInformation("Automatically forwarding port {port} to CS LAN", Port);
var csAdapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(EthernetAdapterType.EthernetCSAdapter); var csAdapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(EthernetAdapterType.EthernetCSAdapter);
var csIp = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, csAdapterId); var csIp = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, csAdapterId);
@@ -164,16 +164,17 @@ namespace PepperDash.Essentials.WebSocketServer
if (result != CrestronEthernetHelper.PortForwardingUserPatRetCodes.NoErr) if (result != CrestronEthernetHelper.PortForwardingUserPatRetCodes.NoErr)
{ {
Debug.LogMessage(LogEventLevel.Error, "Error adding port forwarding: {0}", result); this.LogError("Error adding port forwarding: {error}", result);
} }
} }
catch (ArgumentException) catch (ArgumentException)
{ {
Debug.LogMessage(LogEventLevel.Information, "This processor does not have a CS LAN", this); this.LogInformation("This processor does not have a CS LAN", this);
} }
catch (Exception ex) catch (Exception ex)
{ {
Debug.LogMessage(ex, "Error automatically forwarding port to CS LAN"); this.LogError("Error automatically forwarding port to CS LAN: {message}", ex.Message);
this.LogDebug(ex, "Stack Trace");
} }
} }
@@ -190,7 +191,7 @@ namespace PepperDash.Essentials.WebSocketServer
{ {
if (parent.Config.DirectServer.AutomaticallyForwardPortToCSLAN == false) if (parent.Config.DirectServer.AutomaticallyForwardPortToCSLAN == false)
{ {
Debug.LogMessage(LogEventLevel.Information, "This processor does not have a CS LAN", this); this.LogInformation("This processor does not have a CS LAN");
} }
} }
@@ -259,13 +260,15 @@ namespace PepperDash.Essentials.WebSocketServer
_server.OnPost += Server_OnPost; _server.OnPost += Server_OnPost;
} }
_server.Log.Output = (data, level) => this.LogInformation("WebSocket Server Log [{level}]: {data}", level, data);
CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler; CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler;
_server.Start(); _server.Start();
if (_server.IsListening) if (_server.IsListening)
{ {
Debug.LogMessage(LogEventLevel.Information, "Mobile Control WebSocket Server listening on port {port}", this, _server.Port); this.LogInformation("Mobile Control WebSocket Server listening on port {port}", _server.Port);
} }
CrestronEnvironment.ProgramStatusEventHandler += OnProgramStop; CrestronEnvironment.ProgramStatusEventHandler += OnProgramStop;
@@ -278,7 +281,8 @@ namespace PepperDash.Essentials.WebSocketServer
} }
catch (Exception ex) catch (Exception ex)
{ {
Debug.LogMessage(ex, "Exception intializing websocket server", this); this.LogError("Exception initializing direct server: {message}", ex.Message);
this.LogDebug(ex, "Stack Trace");
} }
} }
@@ -347,22 +351,6 @@ namespace PepperDash.Essentials.WebSocketServer
string ip = processorIp; string ip = processorIp;
// Moved to the MobileControlTouchpanelController class in the GetUrlWithCorrectIp method
// triggered by the Panel.IpInformationChange event so that we know we have the necessary info
// to make the determination of which IP to use.
//if (touchpanel.Touchpanel is IMobileControlCrestronTouchpanelController crestronTouchpanel && csIpAddress != null)
//{
// ip = crestronTouchpanel.ConnectedIps.Any(ipInfo =>
// {
// if (System.Net.IPAddress.TryParse(ipInfo.DeviceIpAddress, out var parsedIp))
// {
// return csIpAddress.IsInSameSubnet(parsedIp, csSubnetMask);
// }
// this.LogWarning("Invalid IP address: {deviceIpAddress}", ipInfo.DeviceIpAddress);
// return false;
// }) ? csIpAddress.ToString() : processorIp;
//}
if (_parent.Config.DirectServer.CSLanUiDeviceKeys != null && _parent.Config.DirectServer.CSLanUiDeviceKeys.Any(k => k.Equals(touchpanel.Touchpanel.Key, StringComparison.InvariantCultureIgnoreCase)) && csIpAddress != null) if (_parent.Config.DirectServer.CSLanUiDeviceKeys != null && _parent.Config.DirectServer.CSLanUiDeviceKeys.Any(k => k.Equals(touchpanel.Touchpanel.Key, StringComparison.InvariantCultureIgnoreCase)) && csIpAddress != null)
{ {
ip = csIpAddress.ToString(); ip = csIpAddress.ToString();
@@ -477,7 +465,8 @@ namespace PepperDash.Essentials.WebSocketServer
} }
catch (Exception ex) catch (Exception ex)
{ {
this.LogError(ex, "Error getting application configuration"); this.LogError("Error getting application configuration: {message}", ex.Message);
this.LogDebug(ex, "Stack Trace");
return null; return null;
} }
@@ -513,15 +502,14 @@ namespace PepperDash.Essentials.WebSocketServer
{ {
if (token.Value == null) if (token.Value == null)
{ {
Debug.LogMessage(LogEventLevel.Warning, "Token value is null", this); this.LogWarning("Token value is null");
continue; continue;
} }
Debug.LogMessage(LogEventLevel.Information, "Adding token: {0} for room: {1}", this, token.Key, token.Value.RoomKey); this.LogInformation("Adding token: {key} for room: {roomKey}", token.Key, token.Value.RoomKey);
if (UiClients == null) if (UiClients == null)
{ {
Debug.LogMessage(LogEventLevel.Warning, "UiClients is null", this);
UiClients = new Dictionary<string, UiClientContext>(); UiClients = new Dictionary<string, UiClientContext>();
} }
@@ -531,7 +519,7 @@ namespace PepperDash.Essentials.WebSocketServer
if (UiClients.Count > 0) if (UiClients.Count > 0)
{ {
Debug.LogMessage(LogEventLevel.Information, "Restored {uiClientCount} UiClients from secrets data", this, UiClients.Count); this.LogInformation("Restored {uiClientCount} UiClients from secrets data", UiClients.Count);
foreach (var client in UiClients) foreach (var client in UiClients)
{ {
@@ -541,36 +529,28 @@ namespace PepperDash.Essentials.WebSocketServer
_server.AddWebSocketService(path, () => _server.AddWebSocketService(path, () =>
{ {
var c = new UiClient(); var c = new UiClient($"uiclient-{key}-{roomKey}");
Debug.LogMessage(LogEventLevel.Debug, "Constructing UiClient with id: {key}", this, key); this.LogDebug("Constructing UiClient with id: {key}", key);
c.Controller = _parent; c.Controller = _parent;
c.RoomKey = roomKey; c.RoomKey = roomKey;
UiClients[key].SetClient(c); UiClients[key].SetClient(c);
return c; return c;
}); });
//_server.WebSocketServices.AddService<UiClient>(path, (c) =>
//{
// Debug.Console(2, this, "Constructing UiClient with id: {0}", key);
// c.Controller = _parent;
// c.RoomKey = roomKey;
// UiClients[key].SetClient(c);
//});
} }
} }
} }
else else
{ {
Debug.LogMessage(LogEventLevel.Warning, "No secret found"); this.LogWarning("No secret found");
} }
Debug.LogMessage(LogEventLevel.Debug, "{uiClientCount} UiClients restored from secrets data", this, UiClients.Count); this.LogDebug("{uiClientCount} UiClients restored from secrets data", UiClients.Count);
} }
catch (Exception ex) catch (Exception ex)
{ {
Debug.LogMessage(ex, "Exception retrieving secret", this); this.LogError("Exception retrieving secret: {message}", ex.Message);
this.LogDebug(ex, "Stack Trace");
} }
} }
@@ -583,7 +563,7 @@ namespace PepperDash.Essentials.WebSocketServer
{ {
if (_secret == null) if (_secret == null)
{ {
Debug.LogMessage(LogEventLevel.Error, "Secret is null", this); this.LogError("Secret is null");
_secret = new ServerTokenSecrets(string.Empty); _secret = new ServerTokenSecrets(string.Empty);
} }
@@ -601,7 +581,8 @@ namespace PepperDash.Essentials.WebSocketServer
} }
catch (Exception ex) catch (Exception ex)
{ {
Debug.LogMessage(ex, "Exception updating secret", this); this.LogError("Exception updating secret: {message}", ex.Message);
this.LogDebug(ex, "Stack Trace");
} }
} }
@@ -704,18 +685,18 @@ namespace PepperDash.Essentials.WebSocketServer
_server.AddWebSocketService(path, () => _server.AddWebSocketService(path, () =>
{ {
var c = new UiClient(); var c = new UiClient($"uiclient-{key}-{bridge.RoomKey}");
Debug.LogMessage(LogEventLevel.Verbose, "Constructing UiClient with id: {0}", this, key); this.LogVerbose("Constructing UiClient with id: {key}", key);
c.Controller = _parent; c.Controller = _parent;
c.RoomKey = bridge.RoomKey; c.RoomKey = bridge.RoomKey;
UiClients[key].SetClient(c); UiClients[key].SetClient(c);
return c; return c;
}); });
Debug.LogMessage(LogEventLevel.Information, "Added new WebSocket UiClient service at path: {path}", this, path); this.LogInformation("Added new WebSocket UiClient service at path: {path}", path);
Debug.LogMessage(LogEventLevel.Information, "Token: {@token}", this, token); this.LogInformation("Token: {@token}", token);
Debug.LogMessage(LogEventLevel.Verbose, "{serviceCount} websocket services present", this, _server.WebSocketServices.Count); this.LogVerbose("{serviceCount} websocket services present", _server.WebSocketServices.Count);
UpdateSecret(); UpdateSecret();
@@ -729,7 +710,7 @@ namespace PepperDash.Essentials.WebSocketServer
{ {
if (s == "?" || string.IsNullOrEmpty(s)) if (s == "?" || string.IsNullOrEmpty(s))
{ {
CrestronConsole.ConsoleCommandResponse(@"Removes all clients from the server. To execute add 'confirm' to command"); CrestronConsole.ConsoleCommandResponse(@"Remove all clients from the server. To execute add 'confirm' to command");
return; return;
} }
@@ -883,7 +864,8 @@ namespace PepperDash.Essentials.WebSocketServer
} }
catch (Exception ex) catch (Exception ex)
{ {
Debug.LogMessage(ex, "Caught an exception in the OnGet handler", this); this.LogError("Exception in OnGet handler: {message}", ex.Message);
this.LogDebug(ex, "Stack Trace");
} }
} }
@@ -1186,7 +1168,7 @@ namespace PepperDash.Essentials.WebSocketServer
} }
else else
{ {
this.LogVerbose("File not found: {filePath}", filePath); this.LogWarning("File not found: {filePath}", filePath);
res.StatusCode = (int)HttpStatusCode.NotFound; res.StatusCode = (int)HttpStatusCode.NotFound;
res.Close(); res.Close();
return; return;
@@ -1256,182 +1238,4 @@ namespace PepperDash.Essentials.WebSocketServer
} }
} }
} }
/// <summary>
/// Represents a Version
/// </summary>
public class Version
{
[JsonProperty("serverVersion")]
public string ServerVersion { get; set; }
[JsonProperty("serverIsRunningOnProcessorHardware")]
public bool ServerIsRunningOnProcessorHardware { get; private set; }
public Version()
{
ServerIsRunningOnProcessorHardware = true;
}
}
/// <summary>
/// Represents a UiClientContext
/// </summary>
public class UiClientContext
{
/// <summary>
/// Gets or sets the Client
/// </summary>
public UiClient Client { get; private set; }
/// <summary>
/// Gets or sets the Token
/// </summary>
public JoinToken Token { get; private set; }
public UiClientContext(JoinToken token)
{
Token = token;
}
/// <summary>
/// SetClient method
/// </summary>
public void SetClient(UiClient client)
{
Client = client;
}
}
/// <summary>
/// Represents a ServerTokenSecrets
/// </summary>
public class ServerTokenSecrets
{
/// <summary>
/// Gets or sets the GrantCode
/// </summary>
public string GrantCode { get; set; }
public Dictionary<string, JoinToken> Tokens { get; set; }
public ServerTokenSecrets(string grantCode)
{
GrantCode = grantCode;
Tokens = new Dictionary<string, JoinToken>();
}
}
/// <summary>
/// Represents a JoinToken
/// </summary>
public class JoinToken
{
/// <summary>
/// Gets or sets the Code
/// </summary>
public string Code { get; set; }
public string RoomKey { get; set; }
public string Uuid { get; set; }
public string TouchpanelKey { get; set; } = "";
/// <summary>
/// Gets or sets the Token
/// </summary>
public string Token { get; set; } = null;
}
/// <summary>
/// Represents a JoinResponse
/// </summary>
public class JoinResponse
{
/// <summary>
/// Gets or sets the ClientId
/// </summary>
[JsonProperty("clientId")]
public string ClientId { get; set; }
[JsonProperty("roomKey")]
public string RoomKey { get; set; }
[JsonProperty("systemUUid")]
public string SystemUuid { get; set; }
/// <summary>
/// Gets or sets the RoomUuid
/// </summary>
[JsonProperty("roomUUid")]
public string RoomUuid { get; set; }
/// <summary>
/// Gets or sets the Config
/// </summary>
[JsonProperty("config")]
public object Config { get; set; }
/// <summary>
/// Gets or sets the DeviceInterfaceSupport
/// </summary>
[JsonProperty("deviceInterfaceSupport")]
public Dictionary<string, DeviceInterfaceInfo> DeviceInterfaceSupport { get; set; }
/// <summary>
/// Gets or sets the CodeExpires
/// </summary>
[JsonProperty("codeExpires")]
public DateTime CodeExpires { get; set; }
/// <summary>
/// Gets or sets the UserCode
/// </summary>
[JsonProperty("userCode")]
public string UserCode { get; set; }
/// <summary>
/// Gets or sets the UserAppUrl
/// </summary>
[JsonProperty("userAppUrl")]
public string UserAppUrl { get; set; }
/// <summary>
/// Gets or sets the EnableDebug
/// </summary>
[JsonProperty("enableDebug")]
public bool EnableDebug { get; set; }
}
/// <summary>
/// Represents info about a device including supproted interfaces
/// </summary>
public class DeviceInterfaceInfo : IKeyName
{
/// <summary>
/// Gets or sets the Key
/// </summary>
[JsonProperty("key")]
public string Key { get; set; }
/// <summary>
/// Gets or sets the Name
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }
/// <summary>
/// Gets or sets the Interfaces
/// </summary>
[JsonProperty("interfaces")]
public List<string> Interfaces { get; set; }
}
} }

View File

@@ -0,0 +1,24 @@
using System.Collections.Generic;
namespace PepperDash.Essentials.WebSocketServer
{
/// <summary>
/// Represents a ServerTokenSecrets
/// </summary>
public class ServerTokenSecrets
{
/// <summary>
/// Gets or sets the GrantCode
/// </summary>
public string GrantCode { get; set; }
public Dictionary<string, JoinToken> Tokens { get; set; }
public ServerTokenSecrets(string grantCode)
{
GrantCode = grantCode;
Tokens = new Dictionary<string, JoinToken>();
}
}
}

View File

@@ -1,23 +1,27 @@
using Newtonsoft.Json; using System;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using PepperDash.Core; using PepperDash.Core;
using PepperDash.Core.Logging;
using PepperDash.Essentials.AppServer.Messengers; using PepperDash.Essentials.AppServer.Messengers;
using PepperDash.Essentials.RoomBridges; using PepperDash.Essentials.RoomBridges;
using Serilog.Events; using Serilog.Events;
using System;
using System.Text.RegularExpressions;
using WebSocketSharp; using WebSocketSharp;
using WebSocketSharp.Server; using WebSocketSharp.Server;
using ErrorEventArgs = WebSocketSharp.ErrorEventArgs; using ErrorEventArgs = WebSocketSharp.ErrorEventArgs;
namespace PepperDash.Essentials.WebSocketServer namespace PepperDash.Essentials.WebSocketServer
{ {
/// <summary> /// <summary>
/// Represents the behaviour to associate with a UiClient for WebSocket communication /// Represents the behaviour to associate with a UiClient for WebSocket communication
/// </summary> /// </summary>
public class UiClient : WebSocketBehavior public class UiClient : WebSocketBehavior, IKeyed
{ {
/// <inheritdoc />
public string Key { get; private set; }
public MobileControlSystemController Controller { get; set; } public MobileControlSystemController Controller { get; set; }
public string RoomKey { get; set; } public string RoomKey { get; set; }
@@ -41,17 +45,18 @@ namespace PepperDash.Essentials.WebSocketServer
} }
} }
public UiClient() public UiClient(string key)
{ {
Key = key;
} }
/// <inheritdoc />
protected override void OnOpen() protected override void OnOpen()
{ {
base.OnOpen(); base.OnOpen();
var url = Context.WebSocket.Url; var url = Context.WebSocket.Url;
Debug.LogMessage(LogEventLevel.Verbose, "New WebSocket Connection from: {0}", null, url); this.LogInformation("New WebSocket Connection from: {url}", url);
var match = Regex.Match(url.AbsoluteUri, "(?:ws|wss):\\/\\/.*(?:\\/mc\\/api\\/ui\\/join\\/)(.*)"); var match = Regex.Match(url.AbsoluteUri, "(?:ws|wss):\\/\\/.*(?:\\/mc\\/api\\/ui\\/join\\/)(.*)");
@@ -117,6 +122,7 @@ namespace PepperDash.Essentials.WebSocketServer
Controller.SendMessageObjectToDirectClient(message); Controller.SendMessageObjectToDirectClient(message);
} }
/// <inheritdoc />
protected override void OnMessage(MessageEventArgs e) protected override void OnMessage(MessageEventArgs e)
{ {
base.OnMessage(e); base.OnMessage(e);
@@ -128,18 +134,21 @@ namespace PepperDash.Essentials.WebSocketServer
} }
} }
/// <inheritdoc />
protected override void OnClose(CloseEventArgs e) protected override void OnClose(CloseEventArgs e)
{ {
base.OnClose(e); base.OnClose(e);
Debug.LogMessage(LogEventLevel.Verbose, "WebSocket UiClient Closing: {0} reason: {1}", null, e.Code, e.Reason); this.LogInformation("WebSocket UiClient Closing: {code} reason: {reason}", e.Code, e.Reason);
} }
/// <inheritdoc />
protected override void OnError(ErrorEventArgs e) protected override void OnError(ErrorEventArgs e)
{ {
base.OnError(e); base.OnError(e);
Debug.LogMessage(LogEventLevel.Verbose, "WebSocket UiClient Error: {exception} message: {message}", e.Exception, e.Message); this.LogError("WebSocket UiClient Error: {message}", e.Message);
this.LogDebug(e.Exception, "Stack Trace");
} }
} }
} }

View File

@@ -0,0 +1,31 @@
namespace PepperDash.Essentials.WebSocketServer
{
/// <summary>
/// Represents a UiClientContext
/// </summary>
public class UiClientContext
{
/// <summary>
/// Gets or sets the Client
/// </summary>
public UiClient Client { get; private set; }
/// <summary>
/// Gets or sets the Token
/// </summary>
public JoinToken Token { get; private set; }
public UiClientContext(JoinToken token)
{
Token = token;
}
/// <summary>
/// SetClient method
/// </summary>
public void SetClient(UiClient client)
{
Client = client;
}
}
}

View File

@@ -0,0 +1,22 @@
using Newtonsoft.Json;
namespace PepperDash.Essentials.WebSocketServer
{
/// <summary>
/// Represents a Version
/// </summary>
public class Version
{
[JsonProperty("serverVersion")]
public string ServerVersion { get; set; }
[JsonProperty("serverIsRunningOnProcessorHardware")]
public bool ServerIsRunningOnProcessorHardware { get; private set; }
public Version()
{
ServerIsRunningOnProcessorHardware = true;
}
}
}