diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 235fee48..7435df6f 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,11 +1,11 @@ - 2.42.1-local + 2.29.0-local $(Version) PepperDash Technology PepperDash Technology PepperDash Essentials - Copyright © 2026 + Copyright © 2025 https://github.com/PepperDash/Essentials git Crestron; 4series @@ -20,7 +20,4 @@ - - - diff --git a/src/PepperDash.Core/Comm/GenericSshClient.cs b/src/PepperDash.Core/Comm/GenericSshClient.cs index 546a2a67..df44ab51 100644 --- a/src/PepperDash.Core/Comm/GenericSshClient.cs +++ b/src/PepperDash.Core/Comm/GenericSshClient.cs @@ -151,8 +151,6 @@ namespace PepperDash.Core // Thread-safety lock for state changes private readonly object _stateLock = new object(); - private volatile bool _isProgramStopping; - private bool disconnectLogged = false; /// @@ -209,9 +207,11 @@ namespace PepperDash.Core { if (programEventType == eProgramStatusEventType.Stopping) { - _isProgramStopping = true; - this.LogDebug("Program stopping. Closing connection"); - Disconnect(); + if (client != null) + { + this.LogDebug("Program stopping. Closing connection"); + Disconnect(); + } } } @@ -228,12 +228,6 @@ namespace PepperDash.Core return; } - if (_isProgramStopping) - { - this.LogDebug("Skipping connect because program is stopping"); - return; - } - ConnectEnabled = true; try @@ -293,7 +287,13 @@ namespace PepperDash.Core } catch (SshConnectionException e) { - var ie = e.InnerException; // The details are inside, when present - remote can close the connection with no inner exception at all + var ie = e.InnerException; // The details are inside!! + + if (ie is SocketException) + { + this.LogError("CONNECTION failure: Cannot reach host"); + this.LogVerbose(ie, "Exception details: "); + } if (ie is System.Net.Sockets.SocketException socketException) { @@ -301,20 +301,20 @@ namespace PepperDash.Core Hostname, Port); this.LogVerbose(socketException, "SocketException details: "); } - else if (ie is SshAuthenticationException) + if (ie is SshAuthenticationException) { this.LogError("Authentication failure for username {userName}", Username); this.LogVerbose(ie, "AuthenticationException details: "); } else { - this.LogError("Error on connect: {error}", ie?.Message ?? e.Message); - this.LogVerbose(ie ?? e, "Exception details: "); + this.LogError("Error on connect: {error}", ie.Message); + this.LogVerbose(ie, "Exception details: "); } disconnectLogged = true; KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); - if (AutoReconnect && ConnectEnabled && !_isProgramStopping) + if (AutoReconnect) { this.LogDebug("Checking autoreconnect: {autoReconnect}, {autoReconnectInterval}ms", AutoReconnect, AutoReconnectIntervalMs); StartReconnectTimer(); @@ -326,7 +326,7 @@ namespace PepperDash.Core disconnectLogged = true; KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); - if (AutoReconnect && ConnectEnabled && !_isProgramStopping) + if (AutoReconnect) { this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); StartReconnectTimer(); @@ -338,7 +338,7 @@ namespace PepperDash.Core this.LogVerbose(e, "Exception details: "); disconnectLogged = true; KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); - if (AutoReconnect && ConnectEnabled && !_isProgramStopping) + if (AutoReconnect) { this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); StartReconnectTimer(); @@ -473,7 +473,7 @@ namespace PepperDash.Core { connectLock.Release(); } - if (AutoReconnect && ConnectEnabled && !_isProgramStopping) + if (AutoReconnect && ConnectEnabled) { this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); StartReconnectTimer(); @@ -516,10 +516,7 @@ namespace PepperDash.Core this.LogError("ObjectDisposedException sending '{message}'. Restarting connection...", text.Trim()); KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); - if (AutoReconnect && ConnectEnabled && !_isProgramStopping) - { - StartReconnectTimer(); - } + StartReconnectTimer(); } catch (Exception ex) { @@ -552,10 +549,7 @@ namespace PepperDash.Core this.LogException(ex, "ObjectDisposedException sending {message}", ComTextHelper.GetEscapedText(bytes)); KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); - if (AutoReconnect && ConnectEnabled && !_isProgramStopping) - { - StartReconnectTimer(); - } + StartReconnectTimer(); } catch (Exception ex) { diff --git a/src/PepperDash.Core/Logging/DebugWebsocketSink.cs b/src/PepperDash.Core/Logging/DebugWebsocketSink.cs index cfbf5785..eeba5772 100644 --- a/src/PepperDash.Core/Logging/DebugWebsocketSink.cs +++ b/src/PepperDash.Core/Logging/DebugWebsocketSink.cs @@ -72,20 +72,6 @@ namespace PepperDash.Core /// public bool IsRunning { get => _httpsServer?.IsListening ?? false; } - /// - /// Gets a value indicating whether there are active WebSocket connections. - /// - public bool HasActiveConnections - { - get - { - if (_httpsServer == null || !_httpsServer.IsListening) return false; - var service = _httpsServer.WebSocketServices[_path]; - if (service == null) return false; - return service.Sessions.Count > 0; - } - } - private readonly ITextFormatter _textFormatter; @@ -231,8 +217,6 @@ namespace PepperDash.Core { Debug.LogInformation("Starting Websocket Server on port: {0}", port); - - Start(port, CertPath, _certificatePassword); } diff --git a/src/PepperDash.Core/PepperDash.Core.csproj b/src/PepperDash.Core/PepperDash.Core.csproj index 747aefb6..daa5c6da 100644 --- a/src/PepperDash.Core/PepperDash.Core.csproj +++ b/src/PepperDash.Core/PepperDash.Core.csproj @@ -43,7 +43,7 @@ - + diff --git a/src/PepperDash.Essentials.Core/Comm and IR/CecPortController.cs b/src/PepperDash.Essentials.Core/Comm and IR/CecPortController.cs index a5aa0d13..4b381dd0 100644 --- a/src/PepperDash.Essentials.Core/Comm and IR/CecPortController.cs +++ b/src/PepperDash.Essentials.Core/Comm and IR/CecPortController.cs @@ -75,9 +75,9 @@ namespace PepperDash.Essentials.Core /// /// Subscribes to the CEC change event once is available. - /// Safe to call repeatedly; the subscription is only wired a single time. - /// If StreamCec is null during construction, this is retried when send methods invoke - /// this method later. + /// Safe to call repeatedly; the subscription is only wired a single time. This allows the + /// receive path to self-heal if StreamCec was null at construction (e.g. the underlying + /// device had not yet come online). /// void TryEnsureCecSubscription() { diff --git a/src/PepperDash.Essentials.Core/Config/Essentials/ConfigReader.cs b/src/PepperDash.Essentials.Core/Config/Essentials/ConfigReader.cs index 37cd4eba..40c6c0cf 100644 --- a/src/PepperDash.Essentials.Core/Config/Essentials/ConfigReader.cs +++ b/src/PepperDash.Essentials.Core/Config/Essentials/ConfigReader.cs @@ -135,14 +135,12 @@ namespace PepperDash.Essentials.Core.Config { var parsedConfig = JObject.Parse(fs.ReadToEnd()); - // A config is v1 if it has separate "system" and "template" nodes that - // need to be merged. A v2 config is already merged by the Portal API and - // will not have "system"/"template" nodes. This is independent of whether - // a "versions" node is present, which only carries version metadata and - // can appear on either a v1 or v2 config. - var isV1Config = parsedConfig["system"] != null && parsedConfig["template"] != null; - - if (!isV1Config) + // Check if it's a v2 config (check for "version" node) + // this means it's already merged by the Portal API + // from the v2 config tool + var isV2Config = parsedConfig["versions"] != null; + + if (isV2Config) { Debug.LogMessage(LogEventLevel.Information, "Config file is a v2 format, no merge necessary."); ConfigObject = parsedConfig.ToObject(); @@ -150,8 +148,6 @@ namespace PepperDash.Essentials.Core.Config return true; } - Debug.LogMessage(LogEventLevel.Information, "Config file is a v1 format, merging system and template."); - // Extract SystemUrl and TemplateUrl into final config output ConfigObject = PortalConfigReader.MergeConfigs(parsedConfig).ToObject(); @@ -164,13 +160,6 @@ namespace PepperDash.Essentials.Core.Config { ConfigObject.TemplateUrl = parsedConfig["template_url"].Value(); } - - // MergeConfigs does not carry the "versions" node forward, so it must be - // applied separately to ensure it's preserved in the merged config. - if (parsedConfig["versions"] != null) - { - ConfigObject.Versions = parsedConfig["versions"].ToObject(); - } } Debug.LogMessage(LogEventLevel.Information, "Successfully Loaded Merged Config"); diff --git a/src/PepperDash.Essentials.Core/Config/Essentials/EssentialsConfig.cs b/src/PepperDash.Essentials.Core/Config/Essentials/EssentialsConfig.cs index 44275ef0..6ffe07d2 100644 --- a/src/PepperDash.Essentials.Core/Config/Essentials/EssentialsConfig.cs +++ b/src/PepperDash.Essentials.Core/Config/Essentials/EssentialsConfig.cs @@ -105,7 +105,6 @@ namespace PepperDash.Essentials.Core.Config /// /// Gets or sets the Versions /// - [JsonProperty("versions")] public VersionData Versions { get; set; } /// @@ -135,25 +134,12 @@ namespace PepperDash.Essentials.Core.Config [JsonProperty("packages")] public List Packages { get; set; } - /// - /// Gets or sets the touchpanel wrapper app version - /// - [JsonProperty("touchpanelWrapperApp")] - public NugetVersion TouchpanelWrapperApp { get; set; } - - /// - /// Gets or sets the list of user interface packages - /// - [JsonProperty("userInterfaces")] - public List UserInterfaces { get; set; } - /// /// Initializes a new instance of the class. /// public VersionData() { Packages = new List(); - UserInterfaces = new List(); } } @@ -171,20 +157,8 @@ namespace PepperDash.Essentials.Core.Config /// /// Gets or sets the PackageId /// - [JsonProperty("packageId", NullValueHandling = NullValueHandling.Ignore)] + [JsonProperty("packageId")] public string PackageId { get; set; } - - /// - /// Gets or sets the RepoUrl - /// - [JsonProperty("repoUrl", NullValueHandling = NullValueHandling.Ignore)] - public string RepoUrl { get; set; } - - /// - /// Gets or sets the human-readable name - /// - [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] - public string Name { get; set; } } /// diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IMobileControlMessengerWithSubscriptions.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IMobileControlMessengerWithSubscriptions.cs index 8603d5b6..887f1789 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IMobileControlMessengerWithSubscriptions.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IMobileControlMessengerWithSubscriptions.cs @@ -1,12 +1,10 @@ -using System; using PepperDash.Core; namespace PepperDash.Essentials.Core.DeviceTypeInterfaces { /// - /// Obsolete: messengers are subscription based by default; use IMobileControlMessenger instead. + /// Defines the contract for IMobileControlMessenger /// - [Obsolete("This interface is obsolete and will be removed in a future version. All messengers are now subscription based.")] public interface IMobileControlMessengerWithSubscriptions : IMobileControlMessenger { /// diff --git a/src/PepperDash.Essentials.Core/Devices/IHasDspPresetSave.cs b/src/PepperDash.Essentials.Core/Devices/IHasDspPresetSave.cs deleted file mode 100644 index c628129e..00000000 --- a/src/PepperDash.Essentials.Core/Devices/IHasDspPresetSave.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace PepperDash.Essentials.Core -{ - /// - /// Defines the contract for IHasDspPresetSave - /// - public interface IHasDspPresetSave : IDspPresets - { - /// - /// Saves the preset by key - /// - /// key of preset to save - void SavePreset(string key); - } -} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/PepperDash.Essentials.Core.csproj b/src/PepperDash.Essentials.Core/PepperDash.Essentials.Core.csproj index b7f333bd..251ba316 100644 --- a/src/PepperDash.Essentials.Core/PepperDash.Essentials.Core.csproj +++ b/src/PepperDash.Essentials.Core/PepperDash.Essentials.Core.csproj @@ -25,7 +25,7 @@ bin\$(Configuration)\PepperDash_Essentials_Core.xml - + diff --git a/src/PepperDash.Essentials.Core/Routing/Extensions.cs b/src/PepperDash.Essentials.Core/Routing/Extensions.cs index 303a0194..2b5b3c8c 100644 --- a/src/PepperDash.Essentials.Core/Routing/Extensions.cs +++ b/src/PepperDash.Essentials.Core/Routing/Extensions.cs @@ -348,9 +348,9 @@ namespace PepperDash.Essentials.Core var sources = DeviceManager.AllDevices.OfType() .Where(d => !(d is IRoutingInputsOutputs)).ToList(); - foreach (var sink in sinks) + foreach (var sink in sinks.Where(d => !(d is IRoutingInputsOutputs))) { - foreach (var source in sources) + foreach (var source in sources.Where(d => !(d is IRoutingInputsOutputs))) { foreach (var inputPort in sink.InputPorts) { diff --git a/src/PepperDash.Essentials.Core/Secrets/SecretsManager.cs b/src/PepperDash.Essentials.Core/Secrets/SecretsManager.cs index ac4e80d8..382f5d55 100644 --- a/src/PepperDash.Essentials.Core/Secrets/SecretsManager.cs +++ b/src/PepperDash.Essentials.Core/Secrets/SecretsManager.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using Crestron.SimplSharp; using PepperDash.Core; -using PepperDash.Core.Logging; using Serilog.Events; namespace PepperDash.Essentials.Core @@ -297,14 +296,19 @@ namespace PepperDash.Essentials.Core { var secretPresent = provider.TestSecret(key); - provider.LogVerbose("SecretsProvider {0} {1} contain a secret entry for {2}", provider.Key, secretPresent ? "does" : "does not", key); + Debug.LogMessage(LogEventLevel.Verbose, provider, "SecretsProvider {0} {1} contain a secret entry for {2}", provider.Key, secretPresent ? "does" : "does not", key); if (!secretPresent) return - $"Unable to update secret for {provider.Key}:{key} - Please use the 'SetSecret' command to modify it"; + String.Format( + "Unable to update secret for {0}:{1} - Please use the 'SetSecret' command to modify it"); var response = provider.SetSecret(key, secret) - ? $"Secret successfully set for {provider.Key}:{key}" - : $"Unable to set secret for {provider.Key}:{key}"; + ? String.Format( + "Secret successfully set for {0}:{1}", + provider.Key, key) + : String.Format( + "Unable to set secret for {0}:{1}", + provider.Key, key); return response; } @@ -312,14 +316,19 @@ namespace PepperDash.Essentials.Core { var secretPresent = provider.TestSecret(key); - provider.LogVerbose("SecretsProvider {0} {1} contain a secret entry for {2}", provider.Key, secretPresent ? "does" : "does not", key); + Debug.LogMessage(LogEventLevel.Verbose, provider, "SecretsProvider {0} {1} contain a secret entry for {2}", provider.Key, secretPresent ? "does" : "does not", key); if (secretPresent) return - $"Unable to set secret for {provider.Key}:{key} - Please use the 'UpdateSecret' command to modify it"; + String.Format( + "Unable to set secret for {0}:{1} - Please use the 'UpdateSecret' command to modify it"); var response = provider.SetSecret(key, secret) - ? $"Secret successfully set for {provider.Key}:{key}" - : $"Unable to set secret for {provider.Key}:{key}"; + ? String.Format( + "Secret successfully set for {0}:{1}", + provider.Key, key) + : String.Format( + "Unable to set secret for {0}:{1}", + provider.Key, key); return response; } @@ -368,10 +377,15 @@ namespace PepperDash.Essentials.Core var key = args[1]; + provider.SetSecret(key, ""); response = provider.SetSecret(key, "") - ? $"Secret successfully deleted for {provider.Key}:{key}" - : $"Unable to delete secret for {provider.Key}:{key}"; + ? String.Format( + "Secret successfully deleted for {0}:{1}", + provider.Key, key) + : String.Format( + "Unable to delete secret for {0}:{1}", + provider.Key, key); CrestronConsole.ConsoleCommandResponse(response); return; diff --git a/src/PepperDash.Essentials.Core/Web/EssentialsWebApi.cs b/src/PepperDash.Essentials.Core/Web/EssentialsWebApi.cs index cfbaa1df..3cdb8433 100644 --- a/src/PepperDash.Essentials.Core/Web/EssentialsWebApi.cs +++ b/src/PepperDash.Essentials.Core/Web/EssentialsWebApi.cs @@ -95,11 +95,6 @@ namespace PepperDash.Essentials.Core.Web Name = "ReportVersions", RouteHandler = new ReportVersionsRequestHandler() }, - new HttpCwsRoute("packageManifest") - { - Name = "GetPackageManifest", - RouteHandler = new GetPackageManifestRequestHandler() - }, new HttpCwsRoute("appdebug") { Name = "AppDebug", diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/DebugSessionRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/DebugSessionRequestHandler.cs index 59c662dc..56c983ae 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/DebugSessionRequestHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/DebugSessionRequestHandler.cs @@ -17,10 +17,7 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers /// Represents a DebugSessionRequestHandler /// public class DebugSessionRequestHandler : WebApiBaseRequestHandler - { - private CTimer _portForwardTimeoutTimer; - private readonly object _timerLock = new object(); - + { /// /// Constructor /// @@ -51,7 +48,6 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0); var port = 0; - string csIp = null; if (!Debug.WebsocketSink.IsRunning) { @@ -61,18 +57,15 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers // Start the WS Server Debug.WebsocketSink.StartServerAndSetPort(port); Debug.SetWebSocketMinimumDebugLevel(Serilog.Events.LogEventLevel.Verbose); - } - // Attempt to get the CS LAN IP and forward the port - try - { - var csAdapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType( - EthernetAdapterType.EthernetCSAdapter); - csIp = CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, csAdapterId); - - if (port > 0) + // Attempt to forward the port to the CS LAN + try { + var csAdapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType( + EthernetAdapterType.EthernetCSAdapter); + var csIp = CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, csAdapterId); + var result = CrestronEthernetHelper.AddPortForwarding( (ushort)port, (ushort)port, csIp, CrestronEthernetHelper.ePortMapTransport.TCP); @@ -84,29 +77,26 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers else { Debug.LogMessage(LogEventLevel.Information, "Port {0} forwarded to CS LAN for debug websocket", port); - StartPortForwardTimeout(port, csIp); } } - } - catch (ArgumentException) - { - Debug.LogMessage(LogEventLevel.Debug, "This processor does not have a CS LAN adapter; skipping port forwarding"); - } - catch (Exception ex) - { - Debug.LogMessage(LogEventLevel.Warning, "Error automatically forwarding debug websocket port to CS LAN: {0}", ex.Message); + catch (ArgumentException) + { + Debug.LogMessage(LogEventLevel.Debug, "This processor does not have a CS LAN adapter; skipping port forwarding"); + } + catch (Exception ex) + { + Debug.LogMessage(LogEventLevel.Warning, "Error automatically forwarding debug websocket port to CS LAN: {0}", ex.Message); + } } var url = Debug.WebsocketSink.Url; - var data = new + object data = new { - url = Debug.WebsocketSink.Url, - fallbackUrl = csIp != null ? url.Replace(csIp, ip) : null + url = Debug.WebsocketSink.Url }; Debug.LogMessage(LogEventLevel.Information, "Debug Session URL: {0}", url); - Debug.LogMessage(LogEventLevel.Information, "Fallback Debug Session URL: {0}", data.fallbackUrl); // Return the port number with the full url of the WS Server var res = JsonConvert.SerializeObject(data); @@ -130,8 +120,6 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers /// protected override void HandlePost(HttpCwsContext context) { - CancelPortForwardTimeout(); - var port = Debug.WebsocketSink.Port; Debug.WebsocketSink.StopServer(); @@ -144,24 +132,17 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers var csIp = CrestronEthernetHelper.GetEthernetParameter( CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, csAdapterId); - if (port <= 0) + var result = CrestronEthernetHelper.RemovePortForwarding( + (ushort)port, (ushort)port, csIp, + CrestronEthernetHelper.ePortMapTransport.TCP); + + if (result != CrestronEthernetHelper.PortForwardingUserPatRetCodes.NoErr) { - Debug.LogMessage(LogEventLevel.Debug, "Debug websocket port is not set; skipping port forwarding removal"); + Debug.LogMessage(LogEventLevel.Warning, "Error removing port forwarding for debug websocket: {0}", result); } else { - var result = CrestronEthernetHelper.RemovePortForwarding( - (ushort)port, (ushort)port, csIp, - CrestronEthernetHelper.ePortMapTransport.TCP); - - if (result != CrestronEthernetHelper.PortForwardingUserPatRetCodes.NoErr) - { - Debug.LogMessage(LogEventLevel.Warning, "Error removing port forwarding for debug websocket: {0}", result); - } - else - { - Debug.LogMessage(LogEventLevel.Information, "Port forwarding for port {0} removed", port); - } + Debug.LogMessage(LogEventLevel.Information, "Port forwarding for port {0} removed", port); } } catch (ArgumentException) @@ -180,55 +161,5 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers Debug.LogMessage(LogEventLevel.Information, "Websocket Debug Session Stopped"); } - private void StartPortForwardTimeout(int port, string csIp) - { - lock (_timerLock) - { - _portForwardTimeoutTimer?.Dispose(); - _portForwardTimeoutTimer = new CTimer(_ => - { - if (Debug.WebsocketSink.HasActiveConnections) - { - Debug.LogMessage(LogEventLevel.Debug, "Debug websocket has active connections; keeping port forward"); - return; - } - - Debug.LogMessage(LogEventLevel.Information, "No debug websocket connection within 30 seconds; removing port forward for port {0}", port); - - try - { - var result = CrestronEthernetHelper.RemovePortForwarding( - (ushort)port, (ushort)port, csIp, - CrestronEthernetHelper.ePortMapTransport.TCP); - - if (result != CrestronEthernetHelper.PortForwardingUserPatRetCodes.NoErr) - { - Debug.LogMessage(LogEventLevel.Warning, "Error removing port forwarding on timeout: {0}", result); - } - else - { - Debug.LogMessage(LogEventLevel.Information, "Port forwarding for port {0} removed due to timeout", port); - } - } - catch (Exception ex) - { - Debug.LogMessage(LogEventLevel.Warning, "Error removing port forwarding on timeout: {0}", ex.Message); - } - }, 30000); - } - } - - /// - /// Cancels the port forward timeout timer if a session is being explicitly stopped. - /// - private void CancelPortForwardTimeout() - { - lock (_timerLock) - { - _portForwardTimeoutTimer?.Dispose(); - _portForwardTimeoutTimer = null; - } - } - } } diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetFeedbacksForDeviceRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetFeedbacksForDeviceRequestHandler.cs index 9831fa91..7d947e19 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetFeedbacksForDeviceRequestHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetFeedbacksForDeviceRequestHandler.cs @@ -52,8 +52,20 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers var device = DeviceManager.GetDeviceForKey(deviceObj.ToString()) as IHasFeedback; if (device == null) { - context.Response.StatusCode = 404; - context.Response.StatusDescription = "Not Found"; + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.ContentType = "application/json"; + context.Response.ContentEncoding = System.Text.Encoding.UTF8; + var resp = new + { + BoolValues = Array.Empty(), + IntValues = Array.Empty(), + SerialValues = Array.Empty() + }; + var respJs = JsonConvert.SerializeObject(resp, Formatting.Indented); + + context.Response.Write(respJs, false); + context.Response.End(); return; diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetPackageManifestRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetPackageManifestRequestHandler.cs deleted file mode 100644 index eeea75cd..00000000 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetPackageManifestRequestHandler.cs +++ /dev/null @@ -1,259 +0,0 @@ -using System; -using System.Linq; -using System.Reflection; -using Crestron.SimplSharp.WebScripting; -using Newtonsoft.Json; -using PepperDash.Core.Web.RequestHandlers; -using PepperDash.Essentials.Core.Config; - -namespace PepperDash.Essentials.Core.Web.RequestHandlers -{ - /// - /// Represents a GetPackageManifestRequestHandler - /// - public class GetPackageManifestRequestHandler : WebApiBaseRequestHandler - { - /// - /// Constructor - /// - /// - /// base(true) enables CORS support by default - /// - public GetPackageManifestRequestHandler() - : base(true) - { - } - - /// - /// Handles GET method requests - /// - /// - protected override void HandleGet(HttpCwsContext context) - { - try - { - var result = CloneVersionData(ConfigReader.ConfigObject?.Versions) ?? new VersionData(); - - PopulateEssentials(result); - PopulatePackages(result); - - var js = JsonConvert.SerializeObject(result, Formatting.Indented); - - context.Response.StatusCode = 200; - context.Response.StatusDescription = "OK"; - context.Response.ContentType = "application/json"; - context.Response.ContentEncoding = System.Text.Encoding.UTF8; - context.Response.Write(js, false); - context.Response.End(); - } - catch (Exception ex) - { - PepperDash.Core.Debug.LogMessage(ex, "Exception handling GET /packageManifest request"); - context.Response.StatusCode = 500; - context.Response.StatusDescription = "Internal Server Error"; - context.Response.End(); - } - } - - /// - /// Deep-copies the config's VersionData so the live config object is never mutated - /// - private static VersionData CloneVersionData(VersionData source) - { - if (source == null) - { - return null; - } - - var json = JsonConvert.SerializeObject(source); - return JsonConvert.DeserializeObject(json); - } - - /// - /// Enriches (or creates) the essentials entry from the loaded PepperDash.Essentials.Core assembly - /// - private static void PopulateEssentials(VersionData result) - { - var essentials = result.Essentials ?? new NugetVersion(); - - essentials.Version = Global.AssemblyVersion; - - // The main program assembly (PackageId "PepperDashEssentials") is what's actually published - // to NuGet, but this handler lives in PepperDash.Essentials.Core, which can't reference that - // project's types directly (Essentials -> Core, not the reverse). PluginLoader.EssentialsAssembly.Assembly - // is unreliable (often left null - see PluginLoader.SetEssentialsAssembly), so look it up - // directly from the loaded AppDomain by its Directory.Build.props-embedded PackageId metadata - // (every project's .csproj sets its own PackageId explicitly), falling back to this handler's - // own (Core) assembly if it can't be found. - var essentialsAssembly = AppDomain.CurrentDomain.GetAssemblies() - .FirstOrDefault(a => string.Equals(GetAssemblyMetadataValue(a, "PackageId"), "PepperDashEssentials", StringComparison.OrdinalIgnoreCase)) - ?? typeof(GetPackageManifestRequestHandler).Assembly; - - var repoUrl = TrimTrailingGit(GetAssemblyMetadataValue(essentialsAssembly, "RepositoryUrl")); - if (!string.IsNullOrEmpty(repoUrl)) - { - essentials.RepoUrl = repoUrl; - } - - var name = GetAssemblyProduct(essentialsAssembly); - if (!string.IsNullOrEmpty(name)) - { - essentials.Name = name; - } - - // Prefer the PackageId embedded via Directory.Build.props' AssemblyMetadata item (the - // authoritative source, matching the actual published PackageId) over any config-supplied - // or hardcoded value. - var reflectedPackageId = GetAssemblyMetadataValue(essentialsAssembly, "PackageId"); - if (!string.IsNullOrEmpty(reflectedPackageId)) - { - essentials.PackageId = reflectedPackageId; - } - else if (string.IsNullOrEmpty(essentials.PackageId)) - { - essentials.PackageId = "PepperDashEssentials"; - } - - result.Essentials = essentials; - } - - /// - /// Merges reflection data from loaded plugin assemblies with the config's packages list - /// - private static void PopulatePackages(VersionData result) - { - // Filter out null entries defensively - the packages list is deserialized from user-editable - // config JSON, so a malformed "packages": [null, ...] shouldn't throw and 500 the endpoint. - var configPackages = (result.Packages ?? new System.Collections.Generic.List()) - .Where(p => p != null) - .ToList(); - var matchedConfigPackages = new System.Collections.Generic.HashSet(); - var mergedPackages = new System.Collections.Generic.List(); - - foreach (var loaded in PluginLoader.EssentialsPluginAssemblies.Where(a => a.Assembly != null)) - { - var reflectedVersion = loaded.Version; - if (string.IsNullOrEmpty(reflectedVersion)) - { - // Never emit an entry with no version - the extension's parser drops entries - // whose version isn't a string. - continue; - } - - var reflectedRepoUrl = TrimTrailingGit(GetAssemblyMetadataValue(loaded.Assembly, "RepositoryUrl")); - var reflectedName = GetAssemblyProduct(loaded.Assembly); - - // Plugins built from a Directory.Build.props that embeds - // carry their PackageId - // directly - this is authoritative and should be preferred over the title/name fallback chain. - var reflectedPackageId = GetAssemblyMetadataValue(loaded.Assembly, "PackageId"); - - var assemblyTitle = GetAssemblyTitle(loaded.Assembly); - var assemblyName = loaded.Assembly.GetName().Name; - var assemblyNameNoSeriesSuffix = StripTrailingSeriesSuffix(assemblyName); - - var match = configPackages.FirstOrDefault(p => - !matchedConfigPackages.Contains(p) && - !string.IsNullOrEmpty(p.PackageId) && - (string.Equals(p.PackageId, reflectedPackageId, StringComparison.OrdinalIgnoreCase) || - string.Equals(p.PackageId, assemblyTitle, StringComparison.OrdinalIgnoreCase) || - string.Equals(p.PackageId, assemblyName, StringComparison.OrdinalIgnoreCase) || - string.Equals(p.PackageId, assemblyNameNoSeriesSuffix, StringComparison.OrdinalIgnoreCase))); - - if (match != null) - { - matchedConfigPackages.Add(match); - - mergedPackages.Add(new NugetVersion - { - Name = !string.IsNullOrEmpty(match.Name) ? match.Name : reflectedName, - RepoUrl = !string.IsNullOrEmpty(match.RepoUrl) ? match.RepoUrl : reflectedRepoUrl, - PackageId = !string.IsNullOrEmpty(reflectedPackageId) ? reflectedPackageId : match.PackageId, - Version = reflectedVersion - }); - } - else - { - // Loaded but not present (or not matched) in config - emit the reflected PackageId - // when the assembly carries one, otherwise leave it null as before. - mergedPackages.Add(new NugetVersion - { - Name = reflectedName, - RepoUrl = reflectedRepoUrl, - PackageId = reflectedPackageId, - Version = reflectedVersion, - }); - } - } - - // Configured but not currently loaded - pass through unchanged - mergedPackages.AddRange(configPackages.Where(p => !matchedConfigPackages.Contains(p))); - - result.Packages = mergedPackages; - } - - private static string GetAssemblyMetadataValue(Assembly assembly, string key) - { - if (assembly == null) - { - return null; - } - - var match = assembly.GetCustomAttributes(typeof(AssemblyMetadataAttribute), false) - .Cast() - .FirstOrDefault(a => string.Equals(a.Key, key, StringComparison.OrdinalIgnoreCase)); - - return match?.Value; - } - - private static string GetAssemblyProduct(Assembly assembly) - { - if (assembly == null) - { - return null; - } - - var attribute = assembly.GetCustomAttributes(typeof(AssemblyProductAttribute), false) - .FirstOrDefault() as AssemblyProductAttribute; - - return attribute?.Product; - } - - private static string GetAssemblyTitle(Assembly assembly) - { - if (assembly == null) - { - return null; - } - - var attribute = assembly.GetCustomAttributes(typeof(AssemblyTitleAttribute), false) - .FirstOrDefault() as AssemblyTitleAttribute; - - return attribute?.Title; - } - - private static string StripTrailingSeriesSuffix(string assemblyName) - { - const string suffix = ".4Series"; - - if (string.IsNullOrEmpty(assemblyName) || !assemblyName.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)) - { - return assemblyName; - } - - return assemblyName.Substring(0, assemblyName.Length - suffix.Length); - } - - private static string TrimTrailingGit(string repoUrl) - { - const string suffix = ".git"; - - if (string.IsNullOrEmpty(repoUrl) || !repoUrl.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)) - { - return repoUrl; - } - - return repoUrl.Substring(0, repoUrl.Length - suffix.Length); - } - } -} diff --git a/src/PepperDash.Essentials.Devices.Common/PepperDash.Essentials.Devices.Common.csproj b/src/PepperDash.Essentials.Devices.Common/PepperDash.Essentials.Devices.Common.csproj index 317fa2d3..7be4372d 100644 --- a/src/PepperDash.Essentials.Devices.Common/PepperDash.Essentials.Devices.Common.csproj +++ b/src/PepperDash.Essentials.Devices.Common/PepperDash.Essentials.Devices.Common.csproj @@ -29,6 +29,6 @@ - + \ No newline at end of file diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/DeviceStateMessageBase.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/DeviceStateMessageBase.cs index c5c0ab65..4241b69c 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/DeviceStateMessageBase.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/DeviceStateMessageBase.cs @@ -13,7 +13,7 @@ namespace PepperDash.Essentials.AppServer.Messengers /// The interfaces implmented by the device sending the messsage /// [JsonProperty("interfaces", NullValueHandling = NullValueHandling.Ignore)] - [Obsolete("Interfaces are no longer supported and will be removed in a future release. Interfaces for all devices are now retrieved via the /joinroom endpoint in the MobileControlWebsocketServer")] + [Obsolete("Interfaces is no longer supported and will be removed in a future release. Interfaces for all devices are now retrieved via the /joinroom endpoint in the MobileControlWebsocketServer")] public List Interfaces { get; private set; } /// diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/MessengerBase.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/MessengerBase.cs index eb3afec3..3031f4ba 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/MessengerBase.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/MessengerBase.cs @@ -264,9 +264,6 @@ namespace PepperDash.Essentials.AppServer.Messengers message.Name = _device.Name; - message.MessageBasePath = MessagePath; - - var token = JToken.FromObject(message); PostStatusMessage(token, MessagePath, clientId); diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/PepperDash.Essentials.MobileControl.Messengers.csproj b/src/PepperDash.Essentials.MobileControl.Messengers/PepperDash.Essentials.MobileControl.Messengers.csproj index a9223061..d13d1a09 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/PepperDash.Essentials.MobileControl.Messengers.csproj +++ b/src/PepperDash.Essentials.MobileControl.Messengers/PepperDash.Essentials.MobileControl.Messengers.csproj @@ -33,7 +33,7 @@ - + diff --git a/src/PepperDash.Essentials.MobileControl/ConnectedClientVersionInfo.cs b/src/PepperDash.Essentials.MobileControl/ConnectedClientVersionInfo.cs deleted file mode 100644 index 68f5e36e..00000000 --- a/src/PepperDash.Essentials.MobileControl/ConnectedClientVersionInfo.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using Newtonsoft.Json; - -namespace PepperDash.Essentials -{ - /// - /// Represents the version information reported by a connected Mobile Control UI client - /// - public class ConnectedClientVersionInfo - { - /// - /// Gets or sets the client id - /// - [JsonProperty("clientId")] - public string ClientId { get; set; } - - /// - /// Gets or sets the room key the client joined - /// - [JsonProperty("roomKey")] - public string RoomKey { get; set; } - - /// - /// Gets or sets the touchpanel key the client joined as, if any - /// - [JsonProperty("touchpanelKey")] - public string TouchpanelKey { get; set; } - - /// - /// Gets or sets the app version reported by the client (e.g. the React app's build-time APP_VERSION) - /// - [JsonProperty("appVersion")] - public string AppVersion { get; set; } - - /// - /// Gets or sets the expected app version from the system config's versions.touchpanelWrapperApp, if configured - /// - [JsonProperty("expectedAppVersion")] - public string ExpectedAppVersion { get; set; } - - /// - /// Gets or sets the UTC time the client last reported this version - /// - [JsonProperty("lastSeen")] - public DateTime LastSeen { get; set; } - - /// - /// Returns a copy of this instance, safe for callers outside the owning lock to hold/mutate - /// without affecting the internally tracked instance - /// - public ConnectedClientVersionInfo Clone() => new ConnectedClientVersionInfo - { - ClientId = ClientId, - RoomKey = RoomKey, - TouchpanelKey = TouchpanelKey, - AppVersion = AppVersion, - ExpectedAppVersion = ExpectedAppVersion, - LastSeen = LastSeen - }; - } -} diff --git a/src/PepperDash.Essentials.MobileControl/MobileControlConfig.cs b/src/PepperDash.Essentials.MobileControl/MobileControlConfig.cs index 963e7fd5..ec7219a3 100644 --- a/src/PepperDash.Essentials.MobileControl/MobileControlConfig.cs +++ b/src/PepperDash.Essentials.MobileControl/MobileControlConfig.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using Newtonsoft.Json; using Newtonsoft.Json.Converters; @@ -41,10 +40,9 @@ namespace PepperDash.Essentials public bool EnableApiServer { get; set; } = true; /// - /// Enables subscriptions for messengers + /// Enable subscriptions for Messengers /// [JsonProperty("enableMessengerSubscriptions")] - [Obsolete("This property is obsolete and will be removed in a future version. All messengers are now subscription based.")] public bool EnableMessengerSubscriptions { get; set; } } @@ -290,4 +288,4 @@ namespace PepperDash.Essentials /// NEO } -} +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs b/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs index c17cf3b9..3d466f5b 100644 --- a/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs +++ b/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs @@ -69,32 +69,11 @@ namespace PepperDash.Essentials private readonly Dictionary _defaultMessengers = new Dictionary(); - private readonly Dictionary _connectedClientVersions = - new Dictionary(StringComparer.InvariantCultureIgnoreCase); - - private readonly object _connectedClientVersionsLock = new object(); - /// /// Get the custom messengers with subscriptions /// public ReadOnlyDictionary Messengers => new ReadOnlyDictionary(_messengers.Values.OfType().ToDictionary(k => k.Key, v => v)); - /// - /// Gets the most recently reported UI app version for each connected client, keyed by clientId - /// - public ReadOnlyDictionary ConnectedClientVersions - { - get - { - lock (_connectedClientVersionsLock) - { - return new ReadOnlyDictionary( - _connectedClientVersions.ToDictionary(kv => kv.Key, kv => kv.Value.Clone()) - ); - } - } - } - /// /// Get the default messengers /// @@ -1803,28 +1782,6 @@ namespace PepperDash.Essentials " Not Enabled in Config.\r\n" ); } - - var connectedClientVersions = ConnectedClientVersions; - - if (connectedClientVersions.Count == 0) - { - CrestronConsole.ConsoleCommandResponse("\r\nUI Client App Versions: None reported yet\r\n"); - } - else - { - CrestronConsole.ConsoleCommandResponse("\r\nUI Client App Versions:\r\n"); - foreach (var kv in connectedClientVersions) - { - var v = kv.Value; - var match = string.IsNullOrEmpty(v.ExpectedAppVersion) || string.Equals(v.ExpectedAppVersion, v.AppVersion, StringComparison.OrdinalIgnoreCase); - - CrestronConsole.ConsoleCommandResponse( - $" Client: {v.ClientId} Touchpanel: {v.TouchpanelKey} Room: {v.RoomKey}\r\n" + - $" Reported: {v.AppVersion} Expected: {(string.IsNullOrEmpty(v.ExpectedAppVersion) ? "(not configured)" : v.ExpectedAppVersion)} Match: {(match ? "Yes" : "NO - MISMATCH")}\r\n" + - $" Last Seen (UTC): {v.LastSeen:yyyy-MM-dd HH:mm:ss}\r\n" - ); - } - } } /// @@ -2224,8 +2181,6 @@ namespace PepperDash.Essentials var roomKey = content["roomKey"].Value(); var touchpanelKey = content.SelectToken("touchpanelKey"); - TrackClientAppVersion(clientId, roomKey, touchpanelKey?.Value(), content.SelectToken("appVersion")?.Value()); - if (_roomCombiner == null) { var message = new MobileControlMessage @@ -2297,50 +2252,6 @@ namespace PepperDash.Essentials SendTouchpanelKey(clientId, touchpanelKey); } - /// - /// Records the app version reported by a connecting UI client (e.g. the mobile control React app's - /// build-time APP_VERSION) and compares it against the configured versions.touchpanelWrapperApp version. - /// - private void TrackClientAppVersion(string clientId, string roomKey, string touchpanelKey, string appVersion) - { - if (string.IsNullOrEmpty(appVersion)) - { - return; - } - - var expectedVersion = ConfigReader.ConfigObject?.Versions?.TouchpanelWrapperApp?.Version; - - var info = new ConnectedClientVersionInfo - { - ClientId = clientId, - RoomKey = roomKey, - TouchpanelKey = touchpanelKey, - AppVersion = appVersion, - ExpectedAppVersion = expectedVersion, - LastSeen = DateTime.UtcNow - }; - - lock (_connectedClientVersionsLock) - { - _connectedClientVersions[clientId] = info; - } - - if (!string.IsNullOrEmpty(expectedVersion) && !string.Equals(expectedVersion, appVersion, StringComparison.OrdinalIgnoreCase)) - { - this.LogWarning( - "Client {clientId} (touchpanel {touchpanelKey}) reported UI app version {appVersion}, which does not match configured versions.touchpanelWrapperApp version {expectedVersion}", - clientId, touchpanelKey, appVersion, expectedVersion - ); - } - else - { - this.LogVerbose( - "Client {clientId} (touchpanel {touchpanelKey}) reported UI app version {appVersion}", - clientId, touchpanelKey, appVersion - ); - } - } - private void SendTouchpanelKey(string clientId, JToken touchpanelKeyToken) { if (touchpanelKeyToken == null) diff --git a/src/PepperDash.Essentials.MobileControl/PepperDash.Essentials.MobileControl.csproj b/src/PepperDash.Essentials.MobileControl/PepperDash.Essentials.MobileControl.csproj index b0e2dd9f..235e0899 100644 --- a/src/PepperDash.Essentials.MobileControl/PepperDash.Essentials.MobileControl.csproj +++ b/src/PepperDash.Essentials.MobileControl/PepperDash.Essentials.MobileControl.csproj @@ -38,7 +38,7 @@ - + diff --git a/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs b/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs index 262cb34e..5830782d 100644 --- a/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs +++ b/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs @@ -25,28 +25,11 @@ namespace PepperDash.Essentials.Touchpanel /// Mobile Control touchpanel controller that provides app control, Zoom integration, /// and mobile control functionality for Crestron touchpanels. /// - public class MobileControlTouchpanelController : TouchpanelBase, IHasFeedback, ITswAppControl, ITswZoomControl, IDeviceInfoProvider, IMobileControlCrestronTouchpanelController, ITheme, ICommunicationMonitor + public class MobileControlTouchpanelController : TouchpanelBase, IHasFeedback, ITswAppControl, ITswZoomControl, IDeviceInfoProvider, IMobileControlCrestronTouchpanelController, ITheme { private readonly MobileControlTouchpanelProperties localConfig; private IMobileControlRoomMessenger _bridge; - /// - /// Gets the CommunicationMonitor tracking the panel's online/offline state - /// - public StatusMonitorBase CommunicationMonitor { get; private set; } - - private sealed class NullCommunicationMonitor : StatusMonitorBase - { - public NullCommunicationMonitor(IKeyed parent) : base(parent, 120000, 300000) - { - Status = MonitorStatus.InError; - Message = "Panel is not initialized"; - } - - public override void Start() { } - public override void Stop() { } - } - private string _appUrl; /// @@ -145,11 +128,6 @@ namespace PepperDash.Essentials.Touchpanel { localConfig = config; - if (panel != null) - { - CommunicationMonitor = new CrestronGenericBaseCommunicationMonitor(this, panel, 120000, 300000); - } - AddPostActivationAction(SubscribeForMobileControlUpdates); ThemeFeedback = new StringFeedback($"{Key}-theme", () => Theme); @@ -388,8 +366,6 @@ namespace PepperDash.Essentials.Touchpanel /// public override bool CustomActivate() { - CommunicationMonitor?.Start(); - var appMessenger = new ITswAppControlMessenger($"appControlMessenger-{Key}", $"/device/{Key}", this); var zoomMessenger = new ITswZoomControlMessenger($"zoomControlMessenger-{Key}", $"/device/{Key}", this); @@ -417,17 +393,6 @@ namespace PepperDash.Essentials.Touchpanel return base.CustomActivate(); } - /// - /// Stops the CommunicationMonitor on deactivation. - /// - /// True if deactivation was successful; otherwise, false. - public override bool Deactivate() - { - CommunicationMonitor?.Stop(); - - return base.Deactivate(); - } - /// /// Handles device extender signal changes for system reserved signals. /// @@ -554,8 +519,15 @@ namespace PepperDash.Essentials.Touchpanel return false; }) ? csIpAddress.ToString() : processorIp; - // replace the host but preserve whatever scheme (http/https) is already present in the URL - var updatedUrl = Regex.Replace(url, @"^(https?)://[^:/]+", $"$1://{ip}"); + var match = Regex.Match(url, @"^http://([^:/]+):\d+/mc/app\?token=.+$"); + if (match.Success) + { + string ipa = match.Groups[1].Value; + // ip will be "192.168.1.100" + } + + // replace ipa with ip but leave the rest of the string intact + var updatedUrl = Regex.Replace(url, @"^http://[^:/]+", $"http://{ip}"); this.LogVerbose("Updated URL: {updatedUrl}", updatedUrl); @@ -768,7 +740,7 @@ namespace PepperDash.Essentials.Touchpanel /// public MobileControlTouchpanelControllerFactory() { - TypeNames = new List() { "mccrestronapp", "mctsw550", "mctsw750", "mctsw1050", "mctsw560", "mctsw760", "mctsw1060", "mctsw570", "mctsw770", "mcts770", "mctsw1070", "mcts1070", "mctsw1080", "mcts1080", "mcxpanel", "mcdge1000" }; + TypeNames = new List() { "mccrestronapp", "mctsw550", "mctsw750", "mctsw1050", "mctsw560", "mctsw760", "mctsw1060", "mctsw570", "mctsw770", "mcts770", "mctsw1070", "mcts1070", "mcxpanel", "mcdge1000" }; MinimumEssentialsFrameworkVersion = "2.0.0"; factories = new Dictionary> @@ -793,8 +765,6 @@ namespace PepperDash.Essentials.Touchpanel {"ts770", (id, controlSystem, projectName) => new Ts770(id, controlSystem)}, {"tsw1070", (id, controlSystem, projectName) => new Tsw1070(id, controlSystem)}, {"ts1070", (id, controlSystem, projectName) => new Ts1070(id, controlSystem)}, - {"tsw1080", (id, controlSystem, projectName) => new Tsw1080(id, controlSystem)}, - {"ts1080", (id, controlSystem, projectName) => new Ts1080(id, controlSystem)}, {"dge1000", (id, controlSystem, projectName) => new Dge1000(id, controlSystem)} }; } diff --git a/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs b/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs index c96dc116..1c9ed37a 100644 --- a/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs +++ b/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs @@ -127,16 +127,6 @@ namespace PepperDash.Essentials.WebSocketServer /// public int Port { get; private set; } - /// - /// Gets the HTTP scheme to use for generated URLs, based on whether the direct server is configured as secure - /// - private string HttpScheme => _parent.Config.DirectServer.Secure ? "https" : "http"; - - /// - /// Gets the WebSocket scheme to use for generated URLs, based on whether the direct server is configured as secure - /// - private string WsScheme => _parent.Config.DirectServer.Secure ? "wss" : "ws"; - /// /// Gets the user app URL prefix /// @@ -144,8 +134,7 @@ namespace PepperDash.Essentials.WebSocketServer { get { - return string.Format("{0}://{1}:{2}{3}?token=", - HttpScheme, + return string.Format("http://{0}:{1}{2}?token=", CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0), Port, _userAppBaseHref); @@ -284,7 +273,7 @@ namespace PepperDash.Essentials.WebSocketServer { base.Initialize(); - _server = new HttpServer(Port, _parent.Config.DirectServer.Secure); + _server = new HttpServer(Port, false); _server.OnGet += Server_OnGet; @@ -302,7 +291,7 @@ namespace PepperDash.Essentials.WebSocketServer { ClientCertificateRequired = false, CheckCertificateRevocation = false, - EnabledSslProtocols = SslProtocols.Tls12 + EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 }; } @@ -414,11 +403,11 @@ namespace PepperDash.Essentials.WebSocketServer ip = csIpAddress.ToString(); } - var appUrl = $"{HttpScheme}://{ip}:{Port}/mc/app?token={touchpanel.Key}"; + var appUrl = $"http://{ip}:{_parent.Config.DirectServer.Port}/mc/app?token={touchpanel.Key}"; this.LogVerbose("Sending URL {appUrl} to touchpanel {touchpanelKey}", appUrl, touchpanel.Touchpanel.Key); - touchpanel.Touchpanel.SetAppUrl(appUrl); + touchpanel.Touchpanel.SetAppUrl($"http://{ip}:{_parent.Config.DirectServer.Port}/mc/app?token={touchpanel.Key}"); } } @@ -498,7 +487,7 @@ namespace PepperDash.Essentials.WebSocketServer { var config = new MobileControlApplicationConfig { - ApiPath = string.Format("{0}://{1}:{2}/mc/api", HttpScheme, processorIp, Port), + ApiPath = string.Format("http://{0}:{1}/mc/api", processorIp, _parent.Config.DirectServer.Port), GatewayAppPath = "", LogoPath = _parent.Config.ApplicationConfig?.LogoPath ?? "logo/logo.png", EnableDev = _parent.Config.ApplicationConfig?.EnableDev ?? false, @@ -1109,7 +1098,6 @@ namespace PepperDash.Essentials.WebSocketServer res.StatusCode = 200; res.Close(); - // remote log collector has no dedicated secure flag; keep it on http regardless of DirectServer.Secure var logRequest = new HttpRequestMessage(HttpMethod.Post, $"http://{_parent.Config.DirectServer.Logging.Host}:{_parent.Config.DirectServer.Logging.Port}/logs") { Content = new StringContent(body, Encoding.UTF8, "application/json"), @@ -1162,11 +1150,6 @@ namespace PepperDash.Essentials.WebSocketServer var qp = req.QueryString; var token = qp["token"]; - // Each join mints a single-use clientId; the panel webview must never replay a cached - // response, or it reconnects forever with an already-consumed id (1008 loop). - res.Headers.Add("Cache-Control", "no-store"); - res.Headers.Add("Pragma", "no-cache"); - this.LogVerbose("Join Room Request with token: {token}", token); byte[] body; @@ -1230,7 +1213,8 @@ namespace PepperDash.Essentials.WebSocketServer this.LogVerbose("Assigning ClientId: {clientId} for token: {token} at {timestamp}", clientId, token, now); // Construct WebSocket URL with clientId query parameter - var wsUrl = $"{WsScheme}://{CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0)}:{Port}{_wsPath}{token}?clientId={clientId}"; + var wsProtocol = "ws"; + var wsUrl = $"{wsProtocol}://{CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0)}:{Port}{_wsPath}{token}?clientId={clientId}"; // Construct the response object JoinResponse jRes = new JoinResponse @@ -1242,8 +1226,7 @@ namespace PepperDash.Essentials.WebSocketServer Config = _parent.GetConfigWithPluginVersion(), CodeExpires = new DateTime().AddYears(1), UserCode = bridge.UserCode, - UserAppUrl = string.Format("{0}://{1}:{2}/mc/app", - HttpScheme, + UserAppUrl = string.Format("http://{0}:{1}/mc/app", CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0), Port), WebSocketUrl = wsUrl, @@ -1268,8 +1251,6 @@ namespace PepperDash.Essentials.WebSocketServer { res.StatusCode = 200; res.ContentType = "application/json"; - res.Headers.Add("Cache-Control", "no-store"); - res.Headers.Add("Pragma", "no-cache"); var version = new Version() { ServerVersion = _parent.GetConfigWithPluginVersion().RuntimeInfo.PluginVersion }; var message = JsonConvert.SerializeObject(version); this.LogVerbose("{message}", message); diff --git a/src/PepperDash.Essentials/PepperDash.Essentials.csproj b/src/PepperDash.Essentials/PepperDash.Essentials.csproj index cb83bf84..20a42ffd 100644 --- a/src/PepperDash.Essentials/PepperDash.Essentials.csproj +++ b/src/PepperDash.Essentials/PepperDash.Essentials.csproj @@ -48,7 +48,7 @@ - +