fix: direct server clients now have unique client IDs

Using the generated security token as an ID was presenting problems with duplicate connections using the same ID and trying to figure out where to send the messages. Now, the clients have a unique ID that's an increasing integer that restarts at 1 when the program restarts.
This commit is contained in:
Andrew Welker
2025-10-15 09:50:12 -05:00
parent 1197b15a33
commit 8525134ae7
10 changed files with 444 additions and 255 deletions

View File

@@ -0,0 +1,90 @@
using System;
using System.Threading;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using PepperDash.Core;
using PepperDash.Core.Logging;
using PepperDash.Essentials.AppServer.Messengers;
using PepperDash.Essentials.Core.Queues;
using PepperDash.Essentials.WebSocketServer;
using Serilog.Events;
namespace PepperDash.Essentials
{
/// <summary>
/// Represents a MessageToClients
/// </summary>
public class MessageToClients : IQueueMessage
{
private readonly MobileControlWebsocketServer _server;
private readonly object msgToSend;
/// <summary>
/// Message to send to Direct Server Clients
/// </summary>
/// <param name="msg">message object to send</param>
/// <param name="server">WebSocket server instance</param>
public MessageToClients(object msg, MobileControlWebsocketServer server)
{
_server = server;
msgToSend = msg;
}
/// <summary>
/// Message to send to Direct Server Clients
/// </summary>
/// <param name="msg">message object to send</param>
/// <param name="server">WebSocket server instance</param>
public MessageToClients(DeviceStateMessageBase msg, MobileControlWebsocketServer server)
{
_server = server;
msgToSend = msg;
}
#region Implementation of IQueueMessage
/// <summary>
/// Dispatch method
/// </summary>
public void Dispatch()
{
try
{
if (_server == null)
{
Debug.LogMessage(LogEventLevel.Warning, "Cannot send message. Server is null");
return;
}
var message = JsonConvert.SerializeObject(msgToSend, Formatting.None,
new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore, Converters = { new IsoDateTimeConverter() } });
var clientSpecificMessage = msgToSend as MobileControlMessage;
if (clientSpecificMessage.ClientId != null)
{
var clientId = clientSpecificMessage.ClientId;
_server.LogVerbose("Message TX To client {clientId}: {message}", clientId, message);
_server.SendMessageToClient(clientId, message);
return;
}
_server.SendMessageToAllClients(message);
_server.LogVerbose("Message TX To all clients: {message}", message);
}
catch (ThreadAbortException)
{
//Swallowing this exception, as it occurs on shutdown and there's no need to print out a scary stack trace
}
catch (Exception ex)
{
Debug.LogMessage(ex, "Caught an exception in the Transmit Processor");
}
}
#endregion
}
}

View File

@@ -1382,33 +1382,13 @@ namespace PepperDash.Essentials
{ {
Log = Log =
{ {
Output = (data, message) => Output = (data, message) => Utilities.ConvertWebsocketLog(data, message, this)
{
switch (data.Level)
{
case LogLevel.Trace:
this.LogVerbose(data.Message);
break;
case LogLevel.Debug:
this.LogDebug(data.Message);
break;
case LogLevel.Info:
this.LogInformation(data.Message);
break;
case LogLevel.Warn:
this.LogWarning(data.Message);
break;
case LogLevel.Error:
this.LogError(data.Message);
break;
case LogLevel.Fatal:
this.LogFatal(data.Message);
break;
}
}
} }
}; };
// setting to trace to let level be controlled by appdebug
_wsClient2.Log.Level = LogLevel.Trace;
_wsClient2.SslConfiguration.EnabledSslProtocols = _wsClient2.SslConfiguration.EnabledSslProtocols =
System.Security.Authentication.SslProtocols.Tls11 System.Security.Authentication.SslProtocols.Tls11
| System.Security.Authentication.SslProtocols.Tls12; | System.Security.Authentication.SslProtocols.Tls12;

View File

@@ -1,13 +1,9 @@
using Newtonsoft.Json; using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters; using Newtonsoft.Json.Converters;
using PepperDash.Core; using PepperDash.Core;
using PepperDash.Core.Logging;
using PepperDash.Essentials.AppServer.Messengers; using PepperDash.Essentials.AppServer.Messengers;
using PepperDash.Essentials.Core.Queues; using PepperDash.Essentials.Core.Queues;
using PepperDash.Essentials.WebSocketServer;
using Serilog.Events;
using System;
using System.Threading;
using WebSocketSharp; using WebSocketSharp;
namespace PepperDash.Essentials namespace PepperDash.Essentials
@@ -20,12 +16,22 @@ namespace PepperDash.Essentials
private readonly WebSocket _ws; private readonly WebSocket _ws;
private readonly object msgToSend; private readonly object msgToSend;
/// <summary>
/// Initialize a message to send
/// </summary>
/// <param name="msg">message object to send</param>
/// <param name="ws">WebSocket instance</param>
public TransmitMessage(object msg, WebSocket ws) public TransmitMessage(object msg, WebSocket ws)
{ {
_ws = ws; _ws = ws;
msgToSend = msg; msgToSend = msg;
} }
/// <summary>
/// Initialize a message to send
/// </summary>
/// <param name="msg">message object to send</param>
/// <param name="ws">WebSocket instance</param>
public TransmitMessage(DeviceStateMessageBase msg, WebSocket ws) public TransmitMessage(DeviceStateMessageBase msg, WebSocket ws)
{ {
_ws = ws; _ws = ws;
@@ -43,13 +49,13 @@ namespace PepperDash.Essentials
{ {
if (_ws == null) if (_ws == null)
{ {
Debug.LogMessage(LogEventLevel.Warning, "Cannot send message. Websocket client is null"); Debug.LogWarning("Cannot send message. Websocket client is null");
return; return;
} }
if (!_ws.IsAlive) if (!_ws.IsAlive)
{ {
Debug.LogMessage(LogEventLevel.Warning, "Cannot send message. Websocket client is not connected"); Debug.LogWarning("Cannot send message. Websocket client is not connected");
return; return;
} }
@@ -57,83 +63,14 @@ namespace PepperDash.Essentials
var message = JsonConvert.SerializeObject(msgToSend, Formatting.None, var message = JsonConvert.SerializeObject(msgToSend, Formatting.None,
new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore, Converters = { new IsoDateTimeConverter() } }); new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore, Converters = { new IsoDateTimeConverter() } });
Debug.LogMessage(LogEventLevel.Verbose, "Message TX: {0}", null, message); Debug.LogVerbose("Message TX: {0}", message);
_ws.Send(message); _ws.Send(message);
} }
catch (Exception ex) catch (Exception ex)
{ {
Debug.LogMessage(ex, "Caught an exception in the Transmit Processor"); Debug.LogError("Caught an exception in the Transmit Processor: {message}", ex.Message);
} Debug.LogDebug(ex, "Stack Trace: ");
}
#endregion
}
/// <summary>
/// Represents a MessageToClients
/// </summary>
public class MessageToClients : IQueueMessage
{
private readonly MobileControlWebsocketServer _server;
private readonly object msgToSend;
public MessageToClients(object msg, MobileControlWebsocketServer server)
{
_server = server;
msgToSend = msg;
}
public MessageToClients(DeviceStateMessageBase msg, MobileControlWebsocketServer server)
{
_server = server;
msgToSend = msg;
}
#region Implementation of IQueueMessage
/// <summary>
/// Dispatch method
/// </summary>
public void Dispatch()
{
try
{
if (_server == null)
{
Debug.LogMessage(LogEventLevel.Warning, "Cannot send message. Server is null");
return;
}
var message = JsonConvert.SerializeObject(msgToSend, Formatting.None,
new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore, Converters = { new IsoDateTimeConverter() } });
var clientSpecificMessage = msgToSend as MobileControlMessage;
if (clientSpecificMessage.ClientId != null)
{
var clientId = clientSpecificMessage.ClientId;
_server.LogVerbose("Message TX To client {clientId} Message: {message}", clientId, message);
_server.SendMessageToClient(clientId, message);
return;
}
_server.SendMessageToAllClients(message);
_server.LogVerbose("Message TX To all clients: {message}", message);
}
catch (ThreadAbortException)
{
//Swallowing this exception, as it occurs on shutdown and there's no need to print out a scary stack trace
}
catch (Exception ex)
{
Debug.LogMessage(ex, "Caught an exception in the Transmit Processor");
} }
} }
#endregion #endregion

View File

@@ -0,0 +1,97 @@
using PepperDash.Core;
using PepperDash.Core.Logging;
using WebSocketSharp;
namespace PepperDash.Essentials
{
/// <summary>
/// Utility functions for logging and other common tasks.
/// </summary>
public static class Utilities
{
private static int nextClientId = 0;
/// <summary>
/// Get
/// </summary>
/// <returns></returns>
public static int GetNextClientId()
{
nextClientId++;
return nextClientId;
}
/// <summary>
/// Converts a WebSocketServer LogData object to Essentials logging calls.
/// </summary>
/// <param name="data">The LogData object to convert.</param>
/// <param name="message">The log message.</param>
/// <param name="device">The device associated with the log message.</param>
public static void ConvertWebsocketLog(LogData data, string message, IKeyed device = null)
{
switch (data.Level)
{
case LogLevel.Trace:
if (device == null)
{
Debug.LogVerbose(message);
}
else
{
device.LogVerbose(message);
}
break;
case LogLevel.Debug:
if (device == null)
{
Debug.LogDebug(message);
}
else
{
device.LogDebug(message);
}
break;
case LogLevel.Info:
if (device == null)
{
Debug.LogInformation(message);
}
else
{
device.LogInformation(message);
}
break;
case LogLevel.Warn:
if (device == null)
{
Debug.LogWarning(message);
}
else
{
device.LogWarning(message);
}
break;
case LogLevel.Error:
if (device == null)
{
Debug.LogError(message);
}
else
{
device.LogError(message);
}
break;
case LogLevel.Fatal:
if (device == null)
{
Debug.LogFatal(message);
}
else
{
device.LogFatal(message);
}
break;
}
}
}
}

View File

@@ -1,12 +1,12 @@
using Crestron.SimplSharp.WebScripting; using System;
using System.Collections.Generic;
using System.Linq;
using Crestron.SimplSharp.WebScripting;
using Newtonsoft.Json; using Newtonsoft.Json;
using PepperDash.Core; using PepperDash.Core;
using PepperDash.Core.Web.RequestHandlers; using PepperDash.Core.Web.RequestHandlers;
using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.Config;
using PepperDash.Essentials.WebSocketServer; using PepperDash.Essentials.WebSocketServer;
using System;
using System.Collections.Generic;
using System.Linq;
namespace PepperDash.Essentials.WebApiHandlers namespace PepperDash.Essentials.WebApiHandlers
{ {
@@ -111,13 +111,13 @@ namespace PepperDash.Essentials.WebApiHandlers
public int ServerPort => directServer.Port; public int ServerPort => directServer.Port;
[JsonProperty("tokensDefined")] [JsonProperty("tokensDefined")]
public int TokensDefined => directServer.UiClients.Count; public int TokensDefined => directServer.UiClientContexts.Count;
[JsonProperty("clientsConnected")] [JsonProperty("clientsConnected")]
public int ClientsConnected => directServer.ConnectedUiClientsCount; public int ClientsConnected => directServer.ConnectedUiClientsCount;
[JsonProperty("clients")] [JsonProperty("clients")]
public List<MobileControlDirectClient> Clients => directServer.UiClients.Select((c, i) => { return new MobileControlDirectClient(c, i, directServer.UserAppUrlPrefix); }).ToList(); public List<MobileControlDirectClient> Clients => directServer.UiClientContexts.Select((c, i) => { return new MobileControlDirectClient(c, i, directServer.UserAppUrlPrefix); }).ToList();
public MobileControlDirectServer(MobileControlWebsocketServer server) public MobileControlDirectServer(MobileControlWebsocketServer server)
{ {

View File

@@ -93,7 +93,7 @@ namespace PepperDash.Essentials.WebApiHandlers
if (!server.UiClients.TryGetValue(request.Token, out UiClientContext clientContext)) if (!server.UiClientContexts.TryGetValue(request.Token, out UiClientContext clientContext))
{ {
var response = new ClientResponse var response = new ClientResponse
{ {
@@ -134,7 +134,7 @@ namespace PepperDash.Essentials.WebApiHandlers
return; return;
} }
server.UiClients.Remove(request.Token); server.UiClientContexts.Remove(request.Token);
server.UpdateSecret(); server.UpdateSecret();

View File

@@ -0,0 +1,24 @@
using System;
namespace PepperDash.Essentials.WebSocketServer
{
/// <summary>
/// Event Args for <see cref="UiClient"/> ConnectionClosed event
/// </summary>
public class ConnectionClosedEventArgs : EventArgs
{
/// <summary>
/// Client ID that is being closed
/// </summary>
public string ClientId { get; private set; }
/// <summary>
/// Initalize an instance of the <see cref="ConnectionClosedEventArgs"/> class.
/// </summary>
/// <param name="clientId">client that's closing</param>
public ConnectionClosedEventArgs(string clientId)
{
ClientId = clientId;
}
}
}

View File

@@ -5,6 +5,10 @@ namespace PepperDash.Essentials.WebSocketServer
/// </summary> /// </summary>
public class JoinToken public class JoinToken
{ {
/// <summary>
/// Unique client ID for a client that is joining
/// </summary>
public string Id { get; set; }
/// <summary> /// <summary>
/// Gets or sets the Code /// Gets or sets the Code
/// </summary> /// </summary>

View File

@@ -10,6 +10,7 @@ using System.Text;
using Crestron.SimplSharp; using Crestron.SimplSharp;
using Crestron.SimplSharp.WebScripting; using Crestron.SimplSharp.WebScripting;
using Newtonsoft.Json; using Newtonsoft.Json;
using Org.BouncyCastle.Crypto.Prng;
using PepperDash.Core; using PepperDash.Core;
using PepperDash.Core.Logging; using PepperDash.Core.Logging;
using PepperDash.Essentials.Core; using PepperDash.Essentials.Core;
@@ -56,7 +57,9 @@ namespace PepperDash.Essentials.WebSocketServer
/// <summary> /// <summary>
/// Gets the collection of UI client contexts /// Gets the collection of UI client contexts
/// </summary> /// </summary>
public Dictionary<string, UiClientContext> UiClients { get; private set; } public Dictionary<string, UiClientContext> UiClientContexts { get; private set; }
private readonly Dictionary<string, UiClient> uiClients = new Dictionary<string, UiClient>();
private readonly MobileControlSystemController _parent; private readonly MobileControlSystemController _parent;
@@ -129,7 +132,7 @@ namespace PepperDash.Essentials.WebSocketServer
{ {
var count = 0; var count = 0;
foreach (var client in UiClients) foreach (var client in UiClientContexts)
{ {
if (client.Value.Client != null && client.Value.Client.Context.WebSocket.IsAlive) if (client.Value.Client != null && client.Value.Client.Context.WebSocket.IsAlive)
{ {
@@ -202,7 +205,7 @@ namespace PepperDash.Essentials.WebSocketServer
} }
UiClients = new Dictionary<string, UiClientContext>(); UiClientContexts = new Dictionary<string, UiClientContext>();
//_joinTokens = new Dictionary<string, JoinToken>(); //_joinTokens = new Dictionary<string, JoinToken>();
@@ -281,27 +284,18 @@ namespace PepperDash.Essentials.WebSocketServer
{ {
switch (data.Level) switch (data.Level)
{ {
case LogLevel.Trace: case LogLevel.Trace: this.LogVerbose(message); break;
this.LogVerbose(data.Message); case LogLevel.Debug: this.LogDebug(message); break;
break; case LogLevel.Info: this.LogInformation(message); break;
case LogLevel.Debug: case LogLevel.Warn: this.LogWarning(message); break;
this.LogDebug(data.Message); case LogLevel.Error: this.LogError(message); break;
break; case LogLevel.Fatal: this.LogFatal(message); break;
case LogLevel.Info:
this.LogInformation(data.Message);
break;
case LogLevel.Warn:
this.LogWarning(data.Message);
break;
case LogLevel.Error:
this.LogError(data.Message);
break;
case LogLevel.Fatal:
this.LogFatal(data.Message);
break;
} }
}; };
// setting to trace to allow logging level to be controlled by appdebug
_server.Log.Level = LogLevel.Trace;
CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler; CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler;
_server.Start(); _server.Start();
@@ -554,20 +548,20 @@ namespace PepperDash.Essentials.WebSocketServer
this.LogInformation("Adding token: {key} for room: {roomKey}", token.Key, token.Value.RoomKey); this.LogInformation("Adding token: {key} for room: {roomKey}", token.Key, token.Value.RoomKey);
if (UiClients == null) if (UiClientContexts == null)
{ {
UiClients = new Dictionary<string, UiClientContext>(); UiClientContexts = new Dictionary<string, UiClientContext>();
} }
UiClients.Add(token.Key, new UiClientContext(token.Value)); UiClientContexts.Add(token.Key, new UiClientContext(token.Value));
} }
} }
if (UiClients.Count > 0) if (UiClientContexts.Count > 0)
{ {
this.LogInformation("Restored {uiClientCount} UiClients from secrets data", UiClients.Count); this.LogInformation("Restored {uiClientCount} UiClients from secrets data", UiClientContexts.Count);
foreach (var client in UiClients) foreach (var client in UiClientContexts)
{ {
var key = client.Key; var key = client.Key;
var path = _wsPath + key; var path = _wsPath + key;
@@ -575,13 +569,8 @@ namespace PepperDash.Essentials.WebSocketServer
_server.AddWebSocketService(path, () => _server.AddWebSocketService(path, () =>
{ {
var c = new UiClient($"uiclient-{key}-{roomKey}"); this.LogInformation("Building a UiClient with ID {id}", client.Value.Token.Id);
this.LogDebug("Constructing UiClient with id: {key}", key); return BuildUiClient(roomKey, client.Value.Token, key);
c.Controller = _parent;
c.RoomKey = roomKey;
UiClients[key].SetClient(c);
return c;
}); });
} }
} }
@@ -591,7 +580,7 @@ namespace PepperDash.Essentials.WebSocketServer
this.LogWarning("No secret found"); this.LogWarning("No secret found");
} }
this.LogDebug("{uiClientCount} UiClients restored from secrets data", UiClients.Count); this.LogDebug("{uiClientCount} UiClients restored from secrets data", UiClientContexts.Count);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -616,7 +605,7 @@ namespace PepperDash.Essentials.WebSocketServer
_secret.Tokens.Clear(); _secret.Tokens.Clear();
foreach (var uiClientContext in UiClients) foreach (var uiClientContext in UiClientContexts)
{ {
_secret.Tokens.Add(uiClientContext.Key, uiClientContext.Value.Token); _secret.Tokens.Add(uiClientContext.Key, uiClientContext.Value.Token);
} }
@@ -725,21 +714,17 @@ namespace PepperDash.Essentials.WebSocketServer
var token = new JoinToken { Code = bridge.UserCode, RoomKey = bridge.RoomKey, Uuid = _parent.SystemUuid, TouchpanelKey = touchPanelKey }; var token = new JoinToken { Code = bridge.UserCode, RoomKey = bridge.RoomKey, Uuid = _parent.SystemUuid, TouchpanelKey = touchPanelKey };
UiClients.Add(key, new UiClientContext(token)); UiClientContexts.Add(key, new UiClientContext(token));
var path = _wsPath + key; var path = _wsPath + key;
_server.AddWebSocketService(path, () => _server.AddWebSocketService(path, () =>
{ {
var c = new UiClient($"uiclient-{key}-{bridge.RoomKey}"); this.LogInformation("Building a UiClient with ID {id}", token.Id);
this.LogVerbose("Constructing UiClient with id: {key}", key); return BuildUiClient(bridge.RoomKey, token, key);
c.Controller = _parent;
c.RoomKey = bridge.RoomKey;
UiClients[key].SetClient(c);
return c;
}); });
this.LogInformation("Added new WebSocket UiClient service at path: {path}", path); this.LogInformation("Added new WebSocket UiClient for path: {path}", path);
this.LogInformation("Token: {@token}", token); this.LogInformation("Token: {@token}", token);
this.LogVerbose("{serviceCount} websocket services present", _server.WebSocketServices.Count); this.LogVerbose("{serviceCount} websocket services present", _server.WebSocketServices.Count);
@@ -749,6 +734,44 @@ namespace PepperDash.Essentials.WebSocketServer
return (key, path); return (key, path);
} }
private UiClient BuildUiClient(string roomKey, JoinToken token, string key)
{
var c = new UiClient($"uiclient-{key}-{roomKey}-{token.Id}", token.Id);
this.LogInformation("Constructing UiClient with key {key} and ID {id}", key, token.Id);
c.Controller = _parent;
c.RoomKey = roomKey;
if (uiClients.ContainsKey(token.Id))
{
this.LogWarning("removing client with duplicate id {id}", token.Id);
uiClients.Remove(token.Id);
}
uiClients.Add(token.Id, c);
// UiClients[key].SetClient(c);
c.ConnectionClosed += (o, a) => uiClients.Remove(a.ClientId);
token.Id = null;
return c;
}
/// <summary>
/// Prints out the session data for each path
/// </summary>
public void PrintSessionData()
{
foreach (var path in _server.WebSocketServices.Paths)
{
this.LogInformation("Path: {path}", path);
this.LogInformation(" Session Count: {sessionCount}", _server.WebSocketServices[path].Sessions.Count);
this.LogInformation(" Active Session Count: {activeSessionCount}", _server.WebSocketServices[path].Sessions.ActiveIDs.Count());
this.LogInformation(" Inactive Session Count: {inactiveSessionCount}", _server.WebSocketServices[path].Sessions.InactiveIDs.Count());
this.LogInformation(" Active Clients:");
foreach (var session in _server.WebSocketServices[path].Sessions.IDs)
{
this.LogInformation(" Client ID: {id}", (_server.WebSocketServices[path].Sessions[session] as UiClient)?.Id);
}
}
}
/// <summary> /// <summary>
/// Removes all clients from the server /// Removes all clients from the server
/// </summary> /// </summary>
@@ -766,7 +789,7 @@ namespace PepperDash.Essentials.WebSocketServer
return; return;
} }
foreach (var client in UiClients) foreach (var client in UiClientContexts)
{ {
if (client.Value.Client != null && client.Value.Client.Context.WebSocket.IsAlive) if (client.Value.Client != null && client.Value.Client.Context.WebSocket.IsAlive)
{ {
@@ -784,7 +807,7 @@ namespace PepperDash.Essentials.WebSocketServer
} }
} }
UiClients.Clear(); UiClientContexts.Clear();
UpdateSecret(); UpdateSecret();
} }
@@ -803,9 +826,9 @@ namespace PepperDash.Essentials.WebSocketServer
var key = s; var key = s;
if (UiClients.ContainsKey(key)) if (UiClientContexts.ContainsKey(key))
{ {
var uiClientContext = UiClients[key]; var uiClientContext = UiClientContexts[key];
if (uiClientContext.Client != null && uiClientContext.Client.Context.WebSocket.IsAlive) if (uiClientContext.Client != null && uiClientContext.Client.Context.WebSocket.IsAlive)
{ {
@@ -815,7 +838,7 @@ namespace PepperDash.Essentials.WebSocketServer
var path = _wsPath + key; var path = _wsPath + key;
if (_server.RemoveWebSocketService(path)) if (_server.RemoveWebSocketService(path))
{ {
UiClients.Remove(key); UiClientContexts.Remove(key);
UpdateSecret(); UpdateSecret();
@@ -839,9 +862,9 @@ namespace PepperDash.Essentials.WebSocketServer
{ {
CrestronConsole.ConsoleCommandResponse("Mobile Control UI Client Info:\r"); CrestronConsole.ConsoleCommandResponse("Mobile Control UI Client Info:\r");
CrestronConsole.ConsoleCommandResponse(string.Format("{0} clients found:\r", UiClients.Count)); CrestronConsole.ConsoleCommandResponse(string.Format("{0} clients found:\r", UiClientContexts.Count));
foreach (var client in UiClients) foreach (var client in UiClientContexts)
{ {
CrestronConsole.ConsoleCommandResponse(string.Format("RoomKey: {0} Token: {1}\r", client.Value.Token.RoomKey, client.Key)); CrestronConsole.ConsoleCommandResponse(string.Format("RoomKey: {0} Token: {1}\r", client.Value.Token.RoomKey, client.Key));
} }
@@ -851,7 +874,7 @@ namespace PepperDash.Essentials.WebSocketServer
{ {
if (programEventType == eProgramStatusEventType.Stopping) if (programEventType == eProgramStatusEventType.Stopping)
{ {
foreach (var client in UiClients.Values) foreach (var client in UiClientContexts.Values)
{ {
if (client.Client != null && client.Client.Context.WebSocket.IsAlive) if (client.Client != null && client.Client.Context.WebSocket.IsAlive)
{ {
@@ -990,13 +1013,34 @@ namespace PepperDash.Essentials.WebSocketServer
this.LogVerbose("Join Room Request with token: {token}", token); this.LogVerbose("Join Room Request with token: {token}", token);
byte[] body;
if (UiClients.TryGetValue(token, out UiClientContext clientContext)) if (!UiClientContexts.TryGetValue(token, out UiClientContext clientContext))
{ {
var message = "Token invalid or has expired";
res.StatusCode = 401;
res.ContentType = "application/json";
this.LogVerbose("{message}", message);
body = Encoding.UTF8.GetBytes(message);
res.ContentLength64 = body.LongLength;
res.Close(body, true);
return;
}
var bridge = _parent.GetRoomBridge(clientContext.Token.RoomKey); var bridge = _parent.GetRoomBridge(clientContext.Token.RoomKey);
if (bridge != null) if (bridge == null)
{ {
var message = string.Format("Unable to find bridge with key: {0}", clientContext.Token.RoomKey);
res.StatusCode = 404;
res.ContentType = "application/json";
this.LogVerbose("{message}", message);
body = Encoding.UTF8.GetBytes(message);
res.ContentLength64 = body.LongLength;
res.Close(body, true);
return;
}
res.StatusCode = 200; res.StatusCode = 200;
res.ContentType = "application/json"; res.ContentType = "application/json";
@@ -1006,18 +1050,24 @@ namespace PepperDash.Essentials.WebSocketServer
foreach (var device in devices) foreach (var device in devices)
{ {
var interfaces = device?.GetType().GetInterfaces().Select((i) => i.Name).ToList() ?? new List<string>(); var interfaces = device?.GetType().GetInterfaces().Select((i) => i.Name).ToList() ?? new List<string>();
deviceInterfaces.Add(device.Key, new DeviceInterfaceInfo deviceInterfaces.Add(device.Key, new DeviceInterfaceInfo
{ {
Key = device.Key, Key = device.Key,
Name = device is IKeyName ? (device as IKeyName).Name : "", Name = (device as IKeyName)?.Name ?? "",
Interfaces = interfaces Interfaces = interfaces
}); });
} }
var clientId = $"{Utilities.GetNextClientId()}";
clientContext.Token.Id = clientId;
this.LogVerbose("Assigning ClientId: {clientId}", clientId);
// Construct the response object // Construct the response object
JoinResponse jRes = new JoinResponse JoinResponse jRes = new JoinResponse
{ {
ClientId = token, ClientId = clientId,
RoomKey = bridge.RoomKey, RoomKey = bridge.RoomKey,
SystemUuid = _parent.SystemUuid, SystemUuid = _parent.SystemUuid,
RoomUuid = _parent.SystemUuid, RoomUuid = _parent.SystemUuid,
@@ -1033,35 +1083,12 @@ namespace PepperDash.Essentials.WebSocketServer
// Serialize to JSON and convert to Byte[] // Serialize to JSON and convert to Byte[]
var json = JsonConvert.SerializeObject(jRes); var json = JsonConvert.SerializeObject(jRes);
var body = Encoding.UTF8.GetBytes(json); body = Encoding.UTF8.GetBytes(json);
res.ContentLength64 = body.LongLength; res.ContentLength64 = body.LongLength;
// Send the response // Send the response
res.Close(body, true); res.Close(body, true);
} }
else
{
var message = string.Format("Unable to find bridge with key: {0}", clientContext.Token.RoomKey);
res.StatusCode = 404;
res.ContentType = "application/json";
this.LogVerbose("{message}", message);
var body = Encoding.UTF8.GetBytes(message);
res.ContentLength64 = body.LongLength;
res.Close(body, true);
}
}
else
{
var message = "Token invalid or has expired";
res.StatusCode = 401;
res.ContentType = "application/json";
this.LogVerbose("{message}", message);
var body = Encoding.UTF8.GetBytes(message);
res.ContentLength64 = body.LongLength;
res.Close(body, true);
}
}
/// <summary> /// <summary>
/// Handles a server version request /// Handles a server version request
@@ -1242,12 +1269,14 @@ namespace PepperDash.Essentials.WebSocketServer
/// </summary> /// </summary>
public void SendMessageToAllClients(string message) public void SendMessageToAllClients(string message)
{ {
foreach (var clientContext in UiClients.Values) foreach (var client in uiClients.Values)
{ {
if (clientContext.Client != null && clientContext.Client.Context.WebSocket.IsAlive) if (!client.Context.WebSocket.IsAlive)
{ {
clientContext.Client.Context.WebSocket.Send(message); continue;
} }
client.Context.WebSocket.Send(message);
} }
} }
@@ -1266,18 +1295,17 @@ namespace PepperDash.Essentials.WebSocketServer
return; return;
} }
if (UiClients.TryGetValue((string)clientId, out UiClientContext clientContext)) if (uiClients.TryGetValue((string)clientId, out var client))
{ {
if (clientContext.Client != null) var socket = client.Context.WebSocket;
{
var socket = clientContext.Client.Context.WebSocket;
if (socket.IsAlive) if (!socket.IsAlive)
{ {
this.LogError("Unable to send message to client {id}. Client is disconnected: {message}", clientId, message);
return;
}
socket.Send(message); socket.Send(message);
} }
}
}
else else
{ {
this.LogWarning("Unable to find client with ID: {clientId}", clientId); this.LogWarning("Unable to find client with ID: {clientId}", clientId);

View File

@@ -64,9 +64,11 @@ namespace PepperDash.Essentials.WebSocketServer
/// Initializes a new instance of the UiClient class with the specified key /// Initializes a new instance of the UiClient class with the specified key
/// </summary> /// </summary>
/// <param name="key">The unique key to identify this client</param> /// <param name="key">The unique key to identify this client</param>
public UiClient(string key) /// <param name="id">The client ID used by the client for this connection</param>
public UiClient(string key, string id)
{ {
Key = key; Key = key;
Id = id;
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -74,19 +76,33 @@ namespace PepperDash.Essentials.WebSocketServer
{ {
base.OnOpen(); base.OnOpen();
var url = Context.WebSocket.Url; Log.Output = (data, message) => Utilities.ConvertWebsocketLog(data, message);
this.LogInformation("New WebSocket Connection from: {url}", url); Log.Level = LogLevel.Trace;
var match = Regex.Match(url.AbsoluteUri, "(?:ws|wss):\\/\\/.*(?:\\/mc\\/api\\/ui\\/join\\/)(.*)"); try
if (!match.Success)
{ {
_connectionTime = DateTime.Now; this.LogDebug("Current session count on open {count}", Sessions.Count);
return; this.LogDebug("Current WebsocketServiceCount on open: {count}", Controller.DirectServer.WebsocketServiceCount);
}
catch (Exception ex)
{
this.LogError("Error getting service count: {message}", ex.Message);
this.LogDebug(ex, "Stack Trace: ");
} }
var clientId = match.Groups[1].Value; // var url = Context.WebSocket.Url;
_clientId = clientId; // this.LogInformation("New WebSocket Connection from: {url}", url);
// var match = Regex.Match(url.AbsoluteUri, "(?:ws|wss):\\/\\/.*(?:\\/mc\\/api\\/ui\\/join\\/)(.*)");
// if (!match.Success)
// {
// _connectionTime = DateTime.Now;
// return;
// }
// var clientId = match.Groups[1].Value;
// _clientId = clientId;
if (Controller == null) if (Controller == null)
{ {
@@ -99,7 +115,7 @@ namespace PepperDash.Essentials.WebSocketServer
Type = "/system/clientJoined", Type = "/system/clientJoined",
Content = JToken.FromObject(new Content = JToken.FromObject(new
{ {
clientId, clientId = Id,
roomKey = RoomKey, roomKey = RoomKey,
}) })
}; };
@@ -110,7 +126,7 @@ namespace PepperDash.Essentials.WebSocketServer
if (bridge == null) return; if (bridge == null) return;
SendUserCodeToClient(bridge, clientId); SendUserCodeToClient(bridge, Id);
bridge.UserCodeChanged -= Bridge_UserCodeChanged; bridge.UserCodeChanged -= Bridge_UserCodeChanged;
bridge.UserCodeChanged += Bridge_UserCodeChanged; bridge.UserCodeChanged += Bridge_UserCodeChanged;
@@ -168,17 +184,30 @@ namespace PepperDash.Essentials.WebSocketServer
{ {
base.OnClose(e); base.OnClose(e);
try
{
this.LogDebug("Current session count on close {count}", Sessions.Count);
this.LogDebug("Current WebsocketServiceCount on close: {count}", Controller.DirectServer.WebsocketServiceCount);
}
catch (Exception ex)
{
this.LogError("Error getting service count: {message}", ex.Message);
this.LogDebug(ex, "Stack Trace: ");
}
this.LogInformation("WebSocket UiClient Closing: {code} reason: {reason}", e.Code, e.Reason); this.LogInformation("WebSocket UiClient Closing: {code} reason: {reason}", e.Code, e.Reason);
foreach (var messenger in Controller.Messengers) foreach (var messenger in Controller.Messengers)
{ {
messenger.Value.UnsubscribeClient(_clientId); messenger.Value.UnsubscribeClient(Id);
} }
foreach (var messenger in Controller.DefaultMessengers) foreach (var messenger in Controller.DefaultMessengers)
{ {
messenger.Value.UnsubscribeClient(_clientId); messenger.Value.UnsubscribeClient(Id);
} }
ConnectionClosed?.Invoke(this, new ConnectionClosedEventArgs(Id));
} }
/// <inheritdoc /> /// <inheritdoc />