diff --git a/src/PepperDash.Core/CoreInterfaces.cs b/src/PepperDash.Core/CoreInterfaces.cs index c334cc00..7eb565dd 100644 --- a/src/PepperDash.Core/CoreInterfaces.cs +++ b/src/PepperDash.Core/CoreInterfaces.cs @@ -15,8 +15,8 @@ namespace PepperDash.Core /// public interface IKeyed { - /// - /// Unique Key + /// + /// Gets the unique key associated with the object. /// [JsonProperty("key")] string Key { get; } @@ -27,8 +27,8 @@ namespace PepperDash.Core /// public interface IKeyName : IKeyed { - /// - /// Isn't it obvious :) + /// + /// Gets the name associated with the current object. /// [JsonProperty("name")] string Name { get; } diff --git a/src/PepperDash.Core/Logging/DebugWebsocketSink.cs b/src/PepperDash.Core/Logging/DebugWebsocketSink.cs index 3372970a..5f4f1de2 100644 --- a/src/PepperDash.Core/Logging/DebugWebsocketSink.cs +++ b/src/PepperDash.Core/Logging/DebugWebsocketSink.cs @@ -1,32 +1,36 @@ -extern alias NewtonsoftJson; +extern alias NewtonsoftJson; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using Crestron.SimplSharp; +using Org.BouncyCastle.Asn1.X509; using Serilog; +using Serilog.Configuration; using Serilog.Core; using Serilog.Events; -using Serilog.Configuration; -using WebSocketSharp.Server; -using Crestron.SimplSharp; -using WebSocketSharp; -using System.Security.Authentication; -using WebSocketSharp.Net; -using X509Certificate2 = System.Security.Cryptography.X509Certificates.X509Certificate2; -using System.IO; -using Org.BouncyCastle.Asn1.X509; using Serilog.Formatting; using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject; using Serilog.Formatting.Json; +using System.IO; +using System.Security.Authentication; +using WebSocketSharp; +using WebSocketSharp.Server; +using X509Certificate2 = System.Security.Cryptography.X509Certificates.X509Certificate2; +using WebSocketSharp.Net; namespace PepperDash.Core { /// - /// Represents a DebugWebsocketSink + /// Provides a WebSocket-based logging sink for debugging purposes, allowing log events to be broadcast to connected + /// WebSocket clients. /// - public class DebugWebsocketSink : ILogEventSink + /// This class implements the interface and is designed to send + /// formatted log events to WebSocket clients connected to a secure WebSocket server. The server is hosted locally + /// and uses a self-signed certificate for SSL/TLS encryption. + public class DebugWebsocketSink : ILogEventSink, IKeyed { private WebSocketServer _wsServer; @@ -34,6 +38,9 @@ namespace PepperDash.Core private const string _certificateName = "selfCres"; private const string _certificatePassword = "cres12345"; + /// + /// Gets the port number on which the HTTPS server is currently running. + /// public int Port { get { @@ -43,6 +50,11 @@ namespace PepperDash.Core } } + /// + /// Gets the WebSocket URL for the current server instance. + /// + /// The URL is dynamically constructed based on the server's current IP address, port, + /// and WebSocket path. public string Url { get @@ -53,20 +65,30 @@ namespace PepperDash.Core } /// - /// Gets or sets the IsRunning + /// Gets a value indicating whether the WebSocket server is currently listening for incoming connections. /// - public bool IsRunning { get => _wsServer?.IsListening ?? false; } - + public bool IsRunning { get => _wsServer?.IsListening ?? false; } + + /// + public string Key => "DebugWebsocketSink"; private readonly ITextFormatter _textFormatter; + /// + /// Initializes a new instance of the class with the specified text formatter. + /// + /// This constructor initializes the WebSocket sink and ensures that a certificate is + /// available for secure communication. If the required certificate does not exist, it will be created + /// automatically. Additionally, the sink is configured to stop the server when the program is + /// stopping. + /// The text formatter used to format log messages. If null, a default JSON formatter is used. public DebugWebsocketSink(ITextFormatter formatProvider) { _textFormatter = formatProvider ?? new JsonFormatter(); if (!File.Exists($"\\user\\{_certificateName}.pfx")) - CreateCert(null); + CreateCert(); CrestronEnvironment.ProgramStatusEventHandler += type => { @@ -77,7 +99,7 @@ namespace PepperDash.Core }; } - private void CreateCert(string[] args) + private static void CreateCert() { try { @@ -109,13 +131,17 @@ namespace PepperDash.Core catch (Exception ex) { //Debug.Console(0, "WSS CreateCert Failed\r\n{0}\r\n{1}", ex.Message, ex.StackTrace); - CrestronConsole.PrintLine(string.Format("WSS CreateCert Failed\r\n{0}\r\n{1}", ex.Message, ex.StackTrace)); + Debug.LogError("WSS CreateCert Failed\r\n{0}\r\n{1}", ex.Message, ex.StackTrace); } } /// - /// Emit method + /// Sends a log event to all connected WebSocket clients. /// + /// The log event is formatted using the configured text formatter and then broadcasted + /// to all clients connected to the WebSocket server. If the WebSocket server is not initialized or not + /// listening, the method exits without performing any action. + /// The log event to be formatted and broadcasted. Cannot be null. public void Emit(LogEvent logEvent) { if (_wsServer == null || !_wsServer.IsListening) return; @@ -123,16 +149,19 @@ namespace PepperDash.Core var sw = new StringWriter(); _textFormatter.Format(logEvent, sw); - _wsServer.WebSocketServices.Broadcast(sw.ToString()); - + _wsServer.WebSocketServices[_path].Sessions.Broadcast(sw.ToString()); } /// - /// StartServerAndSetPort method + /// Starts the WebSocket server on the specified port and configures it with the appropriate certificate. /// + /// This method initializes the WebSocket server and binds it to the specified port. It + /// also applies the server's certificate for secure communication. Ensure that the port is not already in use + /// and that the certificate file is accessible. + /// The port number on which the WebSocket server will listen. Must be a valid, non-negative port number. public void StartServerAndSetPort(int port) { - Debug.Console(0, "Starting Websocket Server on port: {0}", port); + Debug.LogInformation("Starting Websocket Server on port: {0}", port); Start(port, $"\\user\\{_certificateName}.pfx", _certificatePassword); @@ -146,7 +175,7 @@ namespace PepperDash.Core if (!string.IsNullOrWhiteSpace(certPath)) { - Debug.Console(0, "Assigning SSL Configuration"); + Debug.LogInformation("Assigning SSL Configuration"); sslConfig = new ServerSslConfiguration(new X509Certificate2(certPath, certPassword)) { ClientCertificateRequired = false, @@ -155,7 +184,7 @@ namespace PepperDash.Core //this is just to test, you might want to actually validate ClientCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => { - Debug.Console(0, "HTTPS ClientCerticateValidation Callback triggered"); + Debug.LogInformation("HTTPS ClientCerticateValidation Callback triggered"); return true; } }; @@ -167,9 +196,9 @@ namespace PepperDash.Core _wsServer.SslConfiguration.ServerCertificate = sslConfig.ServerCertificate; } - Debug.Console(0, "Adding Debug Client Service"); + Debug.LogInformation("Adding Debug Client Service"); _wsServer.AddWebSocketService(_path); - Debug.Console(0, "Assigning Log Info"); + Debug.LogInformation("Assigning Log Info"); _wsServer.Log.Level = LogLevel.Trace; _wsServer.Log.Output = (d, s) => { @@ -200,36 +229,48 @@ namespace PepperDash.Core break; } - Debug.Console(level, "{1} {0}\rCaller:{2}\rMessage:{3}\rs:{4}", d.Level.ToString(), d.Date.ToString(), d.Caller.ToString(), d.Message, s); + Debug.LogInformation("{1} {0}\rCaller:{2}\rMessage:{3}\rs:{4}", d.Level.ToString(), d.Date.ToString(), d.Caller.ToString(), d.Message, s); }; - Debug.Console(0, "Starting"); + Debug.LogInformation("Starting"); _wsServer.Start(); - Debug.Console(0, "Ready"); + Debug.LogInformation("Ready"); } catch (Exception ex) { - Debug.Console(0, "WebSocket Failed to start {0}", ex.Message); + Debug.LogError("WebSocket Failed to start {0}", ex.Message); } } /// - /// StopServer method + /// Stops the WebSocket server if it is currently running. /// + /// This method halts the WebSocket server and releases any associated resources. After + /// calling this method, the server will no longer accept or process incoming connections. public void StopServer() { - Debug.Console(0, "Stopping Websocket Server"); + Debug.LogInformation("Stopping Websocket Server"); _wsServer?.Stop(); _wsServer = null; } } + /// + /// Configures the logger to write log events to a debug WebSocket sink. + /// + /// This extension method allows you to direct log events to a WebSocket sink for debugging + /// purposes. public static class DebugWebsocketSinkExtensions { /// - /// DebugWebsocketSink method + /// Configures a logger to write log events to a debug WebSocket sink. /// + /// This method adds a sink that writes log events to a WebSocket for debugging purposes. + /// It is typically used during development to stream log events in real-time. + /// The logger sink configuration to apply the WebSocket sink to. + /// An optional text formatter to format the log events. If not provided, a default formatter will be used. + /// A object that can be used to further configure the logger. public static LoggerConfiguration DebugWebsocketSink( this LoggerSinkConfiguration loggerConfiguration, ITextFormatter formatProvider = null) @@ -239,12 +280,19 @@ namespace PepperDash.Core } /// - /// Represents a DebugClient + /// Represents a WebSocket client for debugging purposes, providing connection lifecycle management and message + /// handling functionality. /// + /// The class extends to handle + /// WebSocket connections, including events for opening, closing, receiving messages, and errors. It tracks the + /// duration of the connection and logs relevant events for debugging. public class DebugClient : WebSocketBehavior { private DateTime _connectionTime; + /// + /// Gets the duration of time the WebSocket connection has been active. + /// public TimeSpan ConnectedDuration { get @@ -260,41 +308,49 @@ namespace PepperDash.Core } } + /// + /// Initializes a new instance of the class. + /// + /// This constructor creates a new instance and logs its creation. public DebugClient() { - Debug.Console(0, "DebugClient Created"); + Debug.LogInformation("DebugClient Created"); } + /// protected override void OnOpen() { base.OnOpen(); var url = Context.WebSocket.Url; - Debug.Console(0, Debug.ErrorLogLevel.Notice, "New WebSocket Connection from: {0}", url); + Debug.LogInformation("New WebSocket Connection from: {0}", url); _connectionTime = DateTime.Now; } + /// protected override void OnMessage(MessageEventArgs e) { base.OnMessage(e); - Debug.Console(0, "WebSocket UiClient Message: {0}", e.Data); + Debug.LogInformation("WebSocket UiClient Message: {0}", e.Data); } + /// protected override void OnClose(CloseEventArgs e) { base.OnClose(e); - Debug.Console(0, Debug.ErrorLogLevel.Notice, "WebSocket UiClient Closing: {0} reason: {1}", e.Code, e.Reason); + Debug.LogInformation("WebSocket UiClient Closing: {0} reason: {1}", e.Code, e.Reason); } + /// protected override void OnError(WebSocketSharp.ErrorEventArgs e) { base.OnError(e); - Debug.Console(2, Debug.ErrorLogLevel.Notice, "WebSocket UiClient Error: {0} message: {1}", e.Exception, e.Message); + Debug.LogError("WebSocket UiClient Error: {0} message: {1}", e.Exception, e.Message); } } }