Compare commits

...

9 commits

Author SHA1 Message Date
Neil Dorin
7f05654638
Merge pull request #1469 from PepperDash/hotfix/ssh-stopprog-timing
fix(core-ssh): stop reconnect loop during stopprog
2026-08-20 14:47:33 -06:00
Robert Sanders
4ca59e3186 fix(core-ssh): harden GenericSshClient shutdown reconnect guards 2026-08-20 16:39:20 -04:00
Robert Sanders
a55e4811b3 fix(core-ssh): stop reconnect loop during stopprog 2026-08-20 16:18:04 -04:00
Andrew Welker
5cac0bb6c8
Merge pull request #1468 from PepperDash/fix/ssh-connection-exception 2026-08-20 11:52:03 -05:00
Neil Dorin
44e5ff4c3e
fix: Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-08-20 10:47:30 -06:00
Neil Dorin
ec9753b19e fix(ssh-client): improve error handling for SSH connection exceptions 2026-08-20 10:26:09 -06:00
Jonathan Arndt
12e5ff752d
Merge pull request #1464 from PepperDash/fix-mobile-control-join-response-caching
fix(mobile-control): update version and improve response caching for …
2026-08-19 10:37:32 -07:00
Jonathan Arndt
85a4b635f6 fix(mobile-control): update version and improve response caching for join requests 2026-08-19 10:24:23 -07:00
Jonathan Arndt
aff4c1e897
Merge pull request #1450 from PepperDash/feature/mobile-control-https-wss-support
feat(mobile-control): support https/wss for direct server URLs when Secure is true
2026-08-13 16:27:47 -07:00
3 changed files with 35 additions and 22 deletions

View file

@ -1,6 +1,6 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<Version>2.36.6-local</Version> <Version>2.42.1-local</Version>
<InformationalVersion>$(Version)</InformationalVersion> <InformationalVersion>$(Version)</InformationalVersion>
<Authors>PepperDash Technology</Authors> <Authors>PepperDash Technology</Authors>
<Company>PepperDash Technology</Company> <Company>PepperDash Technology</Company>

View file

@ -151,6 +151,8 @@ namespace PepperDash.Core
// Thread-safety lock for state changes // Thread-safety lock for state changes
private readonly object _stateLock = new object(); private readonly object _stateLock = new object();
private volatile bool _isProgramStopping;
private bool disconnectLogged = false; private bool disconnectLogged = false;
/// <summary> /// <summary>
@ -207,13 +209,11 @@ namespace PepperDash.Core
{ {
if (programEventType == eProgramStatusEventType.Stopping) if (programEventType == eProgramStatusEventType.Stopping)
{ {
if (client != null) _isProgramStopping = true;
{
this.LogDebug("Program stopping. Closing connection"); this.LogDebug("Program stopping. Closing connection");
Disconnect(); Disconnect();
} }
} }
}
/// <summary> /// <summary>
/// Connect method /// Connect method
@ -228,6 +228,12 @@ namespace PepperDash.Core
return; return;
} }
if (_isProgramStopping)
{
this.LogDebug("Skipping connect because program is stopping");
return;
}
ConnectEnabled = true; ConnectEnabled = true;
try try
@ -287,13 +293,7 @@ namespace PepperDash.Core
} }
catch (SshConnectionException e) catch (SshConnectionException e)
{ {
var ie = e.InnerException; // The details are inside!! var ie = e.InnerException; // The details are inside, when present - remote can close the connection with no inner exception at all
if (ie is SocketException)
{
this.LogError("CONNECTION failure: Cannot reach host");
this.LogVerbose(ie, "Exception details: ");
}
if (ie is System.Net.Sockets.SocketException socketException) if (ie is System.Net.Sockets.SocketException socketException)
{ {
@ -301,20 +301,20 @@ namespace PepperDash.Core
Hostname, Port); Hostname, Port);
this.LogVerbose(socketException, "SocketException details: "); this.LogVerbose(socketException, "SocketException details: ");
} }
if (ie is SshAuthenticationException) else if (ie is SshAuthenticationException)
{ {
this.LogError("Authentication failure for username {userName}", Username); this.LogError("Authentication failure for username {userName}", Username);
this.LogVerbose(ie, "AuthenticationException details: "); this.LogVerbose(ie, "AuthenticationException details: ");
} }
else else
{ {
this.LogError("Error on connect: {error}", ie.Message); this.LogError("Error on connect: {error}", ie?.Message ?? e.Message);
this.LogVerbose(ie, "Exception details: "); this.LogVerbose(ie ?? e, "Exception details: ");
} }
disconnectLogged = true; disconnectLogged = true;
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
if (AutoReconnect) if (AutoReconnect && ConnectEnabled && !_isProgramStopping)
{ {
this.LogDebug("Checking autoreconnect: {autoReconnect}, {autoReconnectInterval}ms", AutoReconnect, AutoReconnectIntervalMs); this.LogDebug("Checking autoreconnect: {autoReconnect}, {autoReconnectInterval}ms", AutoReconnect, AutoReconnectIntervalMs);
StartReconnectTimer(); StartReconnectTimer();
@ -326,7 +326,7 @@ namespace PepperDash.Core
disconnectLogged = true; disconnectLogged = true;
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
if (AutoReconnect) if (AutoReconnect && ConnectEnabled && !_isProgramStopping)
{ {
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
StartReconnectTimer(); StartReconnectTimer();
@ -338,7 +338,7 @@ namespace PepperDash.Core
this.LogVerbose(e, "Exception details: "); this.LogVerbose(e, "Exception details: ");
disconnectLogged = true; disconnectLogged = true;
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
if (AutoReconnect) if (AutoReconnect && ConnectEnabled && !_isProgramStopping)
{ {
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
StartReconnectTimer(); StartReconnectTimer();
@ -473,7 +473,7 @@ namespace PepperDash.Core
{ {
connectLock.Release(); connectLock.Release();
} }
if (AutoReconnect && ConnectEnabled) if (AutoReconnect && ConnectEnabled && !_isProgramStopping)
{ {
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
StartReconnectTimer(); StartReconnectTimer();
@ -516,8 +516,11 @@ namespace PepperDash.Core
this.LogError("ObjectDisposedException sending '{message}'. Restarting connection...", text.Trim()); this.LogError("ObjectDisposedException sending '{message}'. Restarting connection...", text.Trim());
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
if (AutoReconnect && ConnectEnabled && !_isProgramStopping)
{
StartReconnectTimer(); StartReconnectTimer();
} }
}
catch (Exception ex) catch (Exception ex)
{ {
this.LogException(ex, "Exception sending text: '{message}'", text); this.LogException(ex, "Exception sending text: '{message}'", text);
@ -549,8 +552,11 @@ namespace PepperDash.Core
this.LogException(ex, "ObjectDisposedException sending {message}", ComTextHelper.GetEscapedText(bytes)); this.LogException(ex, "ObjectDisposedException sending {message}", ComTextHelper.GetEscapedText(bytes));
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
if (AutoReconnect && ConnectEnabled && !_isProgramStopping)
{
StartReconnectTimer(); StartReconnectTimer();
} }
}
catch (Exception ex) catch (Exception ex)
{ {
this.LogException(ex, "Exception sending {message}", ComTextHelper.GetEscapedText(bytes)); this.LogException(ex, "Exception sending {message}", ComTextHelper.GetEscapedText(bytes));

View file

@ -1162,6 +1162,11 @@ namespace PepperDash.Essentials.WebSocketServer
var qp = req.QueryString; var qp = req.QueryString;
var token = qp["token"]; 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); this.LogVerbose("Join Room Request with token: {token}", token);
byte[] body; byte[] body;
@ -1263,6 +1268,8 @@ namespace PepperDash.Essentials.WebSocketServer
{ {
res.StatusCode = 200; res.StatusCode = 200;
res.ContentType = "application/json"; 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 version = new Version() { ServerVersion = _parent.GetConfigWithPluginVersion().RuntimeInfo.PluginVersion };
var message = JsonConvert.SerializeObject(version); var message = JsonConvert.SerializeObject(version);
this.LogVerbose("{message}", message); this.LogVerbose("{message}", message);