From fd70377c7ff8a102753b8c53ef5c8ccc303c896f Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Thu, 25 Sep 2025 08:36:24 -0500 Subject: [PATCH] fix: log errors and disconnects for UI Clients --- .../WebSocketServer/DeviceInterfaceInfo.cs | 28 ++ .../WebSocketServer/JoinResponse.cs | 71 +++++ .../WebSocketServer/JoinToken.cs | 24 ++ .../MobileControlWebsocketServer.cs | 264 +++--------------- .../WebSocketServer/ServerTokenSecrets.cs | 24 ++ .../WebSocketServer/UiClient.cs | 29 +- .../WebSocketServer/UiClientContext.cs | 31 ++ .../WebSocketServer/Version.cs | 22 ++ 8 files changed, 253 insertions(+), 240 deletions(-) create mode 100644 src/PepperDash.Essentials.MobileControl/WebSocketServer/DeviceInterfaceInfo.cs create mode 100644 src/PepperDash.Essentials.MobileControl/WebSocketServer/JoinResponse.cs create mode 100644 src/PepperDash.Essentials.MobileControl/WebSocketServer/JoinToken.cs create mode 100644 src/PepperDash.Essentials.MobileControl/WebSocketServer/ServerTokenSecrets.cs create mode 100644 src/PepperDash.Essentials.MobileControl/WebSocketServer/UiClientContext.cs create mode 100644 src/PepperDash.Essentials.MobileControl/WebSocketServer/Version.cs diff --git a/src/PepperDash.Essentials.MobileControl/WebSocketServer/DeviceInterfaceInfo.cs b/src/PepperDash.Essentials.MobileControl/WebSocketServer/DeviceInterfaceInfo.cs new file mode 100644 index 00000000..09be4399 --- /dev/null +++ b/src/PepperDash.Essentials.MobileControl/WebSocketServer/DeviceInterfaceInfo.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using PepperDash.Core; + + +/// +/// Represents info about a device including supproted interfaces +/// +public class DeviceInterfaceInfo : IKeyName +{ + /// + /// Gets or sets the Key + /// + [JsonProperty("key")] + public string Key { get; set; } + + /// + /// Gets or sets the Name + /// + [JsonProperty("name")] + public string Name { get; set; } + + /// + /// Gets or sets the Interfaces + /// + [JsonProperty("interfaces")] + public List Interfaces { get; set; } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.MobileControl/WebSocketServer/JoinResponse.cs b/src/PepperDash.Essentials.MobileControl/WebSocketServer/JoinResponse.cs new file mode 100644 index 00000000..ce0610e0 --- /dev/null +++ b/src/PepperDash.Essentials.MobileControl/WebSocketServer/JoinResponse.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + + +namespace PepperDash.Essentials.WebSocketServer +{ + /// + /// Represents a JoinResponse + /// + public class JoinResponse + { + + /// + /// Gets or sets the ClientId + /// + [JsonProperty("clientId")] + public string ClientId { get; set; } + + [JsonProperty("roomKey")] + public string RoomKey { get; set; } + + [JsonProperty("systemUUid")] + public string SystemUuid { get; set; } + + + /// + /// Gets or sets the RoomUuid + /// + [JsonProperty("roomUUid")] + public string RoomUuid { get; set; } + + + /// + /// Gets or sets the Config + /// + [JsonProperty("config")] + public object Config { get; set; } + + + /// + /// Gets or sets the CodeExpires + /// + [JsonProperty("codeExpires")] + public DateTime CodeExpires { get; set; } + + + /// + /// Gets or sets the UserCode + /// + [JsonProperty("userCode")] + public string UserCode { get; set; } + + + /// + /// Gets or sets the UserAppUrl + /// + [JsonProperty("userAppUrl")] + public string UserAppUrl { get; set; } + + + /// + /// Gets or sets the EnableDebug + /// + [JsonProperty("enableDebug")] + public bool EnableDebug { get; set; } + + /// + public Dictionary DeviceInterfaceSupport { get; set; } + } +} diff --git a/src/PepperDash.Essentials.MobileControl/WebSocketServer/JoinToken.cs b/src/PepperDash.Essentials.MobileControl/WebSocketServer/JoinToken.cs new file mode 100644 index 00000000..b3ea3c7c --- /dev/null +++ b/src/PepperDash.Essentials.MobileControl/WebSocketServer/JoinToken.cs @@ -0,0 +1,24 @@ +namespace PepperDash.Essentials.WebSocketServer +{ + /// + /// Represents a JoinToken + /// + public class JoinToken + { + /// + /// Gets or sets the Code + /// + public string Code { get; set; } + + public string RoomKey { get; set; } + + public string Uuid { get; set; } + + public string TouchpanelKey { get; set; } = ""; + + /// + /// Gets or sets the Token + /// + public string Token { get; set; } = null; + } +} diff --git a/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs b/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs index 4cb9de84..f2f5003a 100644 --- a/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs +++ b/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs @@ -155,7 +155,7 @@ namespace PepperDash.Essentials.WebSocketServer { 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 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) { - Debug.LogMessage(LogEventLevel.Error, "Error adding port forwarding: {0}", result); + this.LogError("Error adding port forwarding: {error}", result); } } 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) { - 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) { - 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.Log.Output = (data, level) => this.LogInformation("WebSocket Server Log [{level}]: {data}", level, data); + CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler; _server.Start(); 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; @@ -278,7 +281,8 @@ namespace PepperDash.Essentials.WebSocketServer } 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; - // 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) { ip = csIpAddress.ToString(); @@ -477,7 +465,8 @@ namespace PepperDash.Essentials.WebSocketServer } 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; } @@ -513,15 +502,14 @@ namespace PepperDash.Essentials.WebSocketServer { if (token.Value == null) { - Debug.LogMessage(LogEventLevel.Warning, "Token value is null", this); + this.LogWarning("Token value is null"); 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) { - Debug.LogMessage(LogEventLevel.Warning, "UiClients is null", this); UiClients = new Dictionary(); } @@ -531,7 +519,7 @@ namespace PepperDash.Essentials.WebSocketServer 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) { @@ -541,36 +529,28 @@ namespace PepperDash.Essentials.WebSocketServer _server.AddWebSocketService(path, () => { - var c = new UiClient(); - Debug.LogMessage(LogEventLevel.Debug, "Constructing UiClient with id: {key}", this, key); + var c = new UiClient($"uiclient-{key}-{roomKey}"); + this.LogDebug("Constructing UiClient with id: {key}", key); c.Controller = _parent; c.RoomKey = roomKey; UiClients[key].SetClient(c); return c; }); - - - //_server.WebSocketServices.AddService(path, (c) => - //{ - // Debug.Console(2, this, "Constructing UiClient with id: {0}", key); - // c.Controller = _parent; - // c.RoomKey = roomKey; - // UiClients[key].SetClient(c); - //}); } } } 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) { - 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) { - Debug.LogMessage(LogEventLevel.Error, "Secret is null", this); + this.LogError("Secret is null"); _secret = new ServerTokenSecrets(string.Empty); } @@ -601,7 +581,8 @@ namespace PepperDash.Essentials.WebSocketServer } 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, () => { - var c = new UiClient(); - Debug.LogMessage(LogEventLevel.Verbose, "Constructing UiClient with id: {0}", this, key); + var c = new UiClient($"uiclient-{key}-{bridge.RoomKey}"); + this.LogVerbose("Constructing UiClient with id: {key}", key); c.Controller = _parent; c.RoomKey = bridge.RoomKey; UiClients[key].SetClient(c); return c; }); - Debug.LogMessage(LogEventLevel.Information, "Added new WebSocket UiClient service at path: {path}", this, path); - Debug.LogMessage(LogEventLevel.Information, "Token: {@token}", this, token); + this.LogInformation("Added new WebSocket UiClient service at path: {path}", path); + 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(); @@ -729,7 +710,7 @@ namespace PepperDash.Essentials.WebSocketServer { 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; } @@ -883,7 +864,8 @@ namespace PepperDash.Essentials.WebSocketServer } 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 { - this.LogVerbose("File not found: {filePath}", filePath); + this.LogWarning("File not found: {filePath}", filePath); res.StatusCode = (int)HttpStatusCode.NotFound; res.Close(); return; @@ -1256,182 +1238,4 @@ namespace PepperDash.Essentials.WebSocketServer } } } - - /// - /// Represents a Version - /// - public class Version - { - [JsonProperty("serverVersion")] - public string ServerVersion { get; set; } - - [JsonProperty("serverIsRunningOnProcessorHardware")] - public bool ServerIsRunningOnProcessorHardware { get; private set; } - - public Version() - { - ServerIsRunningOnProcessorHardware = true; - } - } - - /// - /// Represents a UiClientContext - /// - public class UiClientContext - { - /// - /// Gets or sets the Client - /// - public UiClient Client { get; private set; } - /// - /// Gets or sets the Token - /// - public JoinToken Token { get; private set; } - - public UiClientContext(JoinToken token) - { - Token = token; - } - - /// - /// SetClient method - /// - public void SetClient(UiClient client) - { - Client = client; - } - - } - - /// - /// Represents a ServerTokenSecrets - /// - public class ServerTokenSecrets - { - /// - /// Gets or sets the GrantCode - /// - public string GrantCode { get; set; } - - public Dictionary Tokens { get; set; } - - public ServerTokenSecrets(string grantCode) - { - GrantCode = grantCode; - Tokens = new Dictionary(); - } - } - - /// - /// Represents a JoinToken - /// - public class JoinToken - { - /// - /// Gets or sets the Code - /// - public string Code { get; set; } - - public string RoomKey { get; set; } - - public string Uuid { get; set; } - - public string TouchpanelKey { get; set; } = ""; - - /// - /// Gets or sets the Token - /// - public string Token { get; set; } = null; - } - - /// - /// Represents a JoinResponse - /// - public class JoinResponse - { - - /// - /// Gets or sets the ClientId - /// - [JsonProperty("clientId")] - public string ClientId { get; set; } - - [JsonProperty("roomKey")] - public string RoomKey { get; set; } - - [JsonProperty("systemUUid")] - public string SystemUuid { get; set; } - - - /// - /// Gets or sets the RoomUuid - /// - [JsonProperty("roomUUid")] - public string RoomUuid { get; set; } - - - /// - /// Gets or sets the Config - /// - [JsonProperty("config")] - public object Config { get; set; } - - /// - /// Gets or sets the DeviceInterfaceSupport - /// - [JsonProperty("deviceInterfaceSupport")] - public Dictionary DeviceInterfaceSupport { get; set; } - - - /// - /// Gets or sets the CodeExpires - /// - [JsonProperty("codeExpires")] - public DateTime CodeExpires { get; set; } - - - /// - /// Gets or sets the UserCode - /// - [JsonProperty("userCode")] - public string UserCode { get; set; } - - - /// - /// Gets or sets the UserAppUrl - /// - [JsonProperty("userAppUrl")] - public string UserAppUrl { get; set; } - - - /// - /// Gets or sets the EnableDebug - /// - [JsonProperty("enableDebug")] - public bool EnableDebug { get; set; } - } - - /// - /// Represents info about a device including supproted interfaces - /// - public class DeviceInterfaceInfo : IKeyName - { - /// - /// Gets or sets the Key - /// - [JsonProperty("key")] - public string Key { get; set; } - - /// - /// Gets or sets the Name - /// - [JsonProperty("name")] - public string Name { get; set; } - - /// - /// Gets or sets the Interfaces - /// - [JsonProperty("interfaces")] - public List Interfaces { get; set; } - } } diff --git a/src/PepperDash.Essentials.MobileControl/WebSocketServer/ServerTokenSecrets.cs b/src/PepperDash.Essentials.MobileControl/WebSocketServer/ServerTokenSecrets.cs new file mode 100644 index 00000000..3fa2fb0c --- /dev/null +++ b/src/PepperDash.Essentials.MobileControl/WebSocketServer/ServerTokenSecrets.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; + + +namespace PepperDash.Essentials.WebSocketServer +{ + /// + /// Represents a ServerTokenSecrets + /// + public class ServerTokenSecrets + { + /// + /// Gets or sets the GrantCode + /// + public string GrantCode { get; set; } + + public Dictionary Tokens { get; set; } + + public ServerTokenSecrets(string grantCode) + { + GrantCode = grantCode; + Tokens = new Dictionary(); + } + } +} diff --git a/src/PepperDash.Essentials.MobileControl/WebSocketServer/UiClient.cs b/src/PepperDash.Essentials.MobileControl/WebSocketServer/UiClient.cs index eb1cf7a1..d1d4524d 100644 --- a/src/PepperDash.Essentials.MobileControl/WebSocketServer/UiClient.cs +++ b/src/PepperDash.Essentials.MobileControl/WebSocketServer/UiClient.cs @@ -1,23 +1,27 @@ -using Newtonsoft.Json; +using System; +using System.Text.RegularExpressions; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using PepperDash.Core; +using PepperDash.Core.Logging; using PepperDash.Essentials.AppServer.Messengers; using PepperDash.Essentials.RoomBridges; using Serilog.Events; -using System; -using System.Text.RegularExpressions; using WebSocketSharp; using WebSocketSharp.Server; using ErrorEventArgs = WebSocketSharp.ErrorEventArgs; namespace PepperDash.Essentials.WebSocketServer -{ +{ /// /// Represents the behaviour to associate with a UiClient for WebSocket communication /// - public class UiClient : WebSocketBehavior + public class UiClient : WebSocketBehavior, IKeyed { + /// + public string Key { get; private set; } + public MobileControlSystemController Controller { get; set; } public string RoomKey { get; set; } @@ -41,17 +45,18 @@ namespace PepperDash.Essentials.WebSocketServer } } - public UiClient() + public UiClient(string key) { - + Key = key; } + /// protected override void OnOpen() { base.OnOpen(); 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\\/)(.*)"); @@ -117,6 +122,7 @@ namespace PepperDash.Essentials.WebSocketServer Controller.SendMessageObjectToDirectClient(message); } + /// protected override void OnMessage(MessageEventArgs e) { base.OnMessage(e); @@ -128,18 +134,21 @@ namespace PepperDash.Essentials.WebSocketServer } } + /// protected override void OnClose(CloseEventArgs 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); } + /// protected override void OnError(ErrorEventArgs 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"); } } } diff --git a/src/PepperDash.Essentials.MobileControl/WebSocketServer/UiClientContext.cs b/src/PepperDash.Essentials.MobileControl/WebSocketServer/UiClientContext.cs new file mode 100644 index 00000000..6782306f --- /dev/null +++ b/src/PepperDash.Essentials.MobileControl/WebSocketServer/UiClientContext.cs @@ -0,0 +1,31 @@ +namespace PepperDash.Essentials.WebSocketServer +{ + /// + /// Represents a UiClientContext + /// + public class UiClientContext + { + /// + /// Gets or sets the Client + /// + public UiClient Client { get; private set; } + /// + /// Gets or sets the Token + /// + public JoinToken Token { get; private set; } + + public UiClientContext(JoinToken token) + { + Token = token; + } + + /// + /// SetClient method + /// + public void SetClient(UiClient client) + { + Client = client; + } + + } +} diff --git a/src/PepperDash.Essentials.MobileControl/WebSocketServer/Version.cs b/src/PepperDash.Essentials.MobileControl/WebSocketServer/Version.cs new file mode 100644 index 00000000..5552e29b --- /dev/null +++ b/src/PepperDash.Essentials.MobileControl/WebSocketServer/Version.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; + + +namespace PepperDash.Essentials.WebSocketServer +{ + /// + /// Represents a Version + /// + public class Version + { + [JsonProperty("serverVersion")] + public string ServerVersion { get; set; } + + [JsonProperty("serverIsRunningOnProcessorHardware")] + public bool ServerIsRunningOnProcessorHardware { get; private set; } + + public Version() + { + ServerIsRunningOnProcessorHardware = true; + } + } +}