mirror of
https://github.com/PepperDash/Essentials.git
synced 2026-01-29 04:15:00 +00:00
feat: unique client IDs for direct server
When multiple UI applications were connecting using the same token, the actual websocket connection was getting lost, and could eventually be garbage-collected, leading to odd behavior from the UI. This is due to an existing client getting replaced and a reference to it lost. This has now been remedied, with each client getting a unique instance with a unique client ID.
This commit is contained in:
74
src/PepperDash.Essentials.MobileControl/MessageToClients.cs
Normal file
74
src/PepperDash.Essentials.MobileControl/MessageToClients.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
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
|
||||
{
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,10 @@
|
||||
using Crestron.SimplSharp;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronIO;
|
||||
using Crestron.SimplSharp.Net.Http;
|
||||
using Crestron.SimplSharp.WebScripting;
|
||||
@@ -30,12 +36,6 @@ using PepperDash.Essentials.RoomBridges;
|
||||
using PepperDash.Essentials.Services;
|
||||
using PepperDash.Essentials.WebApiHandlers;
|
||||
using PepperDash.Essentials.WebSocketServer;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using WebSocketSharp;
|
||||
|
||||
namespace PepperDash.Essentials
|
||||
@@ -570,7 +570,7 @@ namespace PepperDash.Essentials
|
||||
{
|
||||
this.LogVerbose(
|
||||
"Adding ISetTopBoxControlMessenger for {deviceKey}"
|
||||
);
|
||||
);
|
||||
|
||||
var messenger = new ISetTopBoxControlsMessenger(
|
||||
$"{device.Key}-stb-{Key}",
|
||||
@@ -587,7 +587,7 @@ namespace PepperDash.Essentials
|
||||
{
|
||||
this.LogVerbose(
|
||||
"Adding IChannelMessenger for {deviceKey}", device.Key
|
||||
);
|
||||
);
|
||||
|
||||
var messenger = new IChannelMessenger(
|
||||
$"{device.Key}-channel-{Key}",
|
||||
@@ -602,7 +602,7 @@ namespace PepperDash.Essentials
|
||||
|
||||
if (device is IColor colorDevice)
|
||||
{
|
||||
this.LogVerbose("Adding IColorMessenger for {deviceKey}", device.Key);
|
||||
this.LogVerbose("Adding IColorMessenger for {deviceKey}", device.Key);
|
||||
|
||||
var messenger = new IColorMessenger(
|
||||
$"{device.Key}-color-{Key}",
|
||||
@@ -617,7 +617,7 @@ namespace PepperDash.Essentials
|
||||
|
||||
if (device is IDPad dPadDevice)
|
||||
{
|
||||
this.LogVerbose("Adding IDPadMessenger for {deviceKey}", device.Key);
|
||||
this.LogVerbose("Adding IDPadMessenger for {deviceKey}", device.Key);
|
||||
|
||||
var messenger = new IDPadMessenger(
|
||||
$"{device.Key}-dPad-{Key}",
|
||||
@@ -632,7 +632,7 @@ namespace PepperDash.Essentials
|
||||
|
||||
if (device is INumericKeypad nkDevice)
|
||||
{
|
||||
this.LogVerbose("Adding INumericKeyapdMessenger for {deviceKey}", device.Key);
|
||||
this.LogVerbose("Adding INumericKeyapdMessenger for {deviceKey}", device.Key);
|
||||
|
||||
var messenger = new INumericKeypadMessenger(
|
||||
$"{device.Key}-numericKeypad-{Key}",
|
||||
@@ -647,7 +647,7 @@ namespace PepperDash.Essentials
|
||||
|
||||
if (device is IHasPowerControl pcDevice)
|
||||
{
|
||||
this.LogVerbose("Adding IHasPowerControlMessenger for {deviceKey}", device.Key);
|
||||
this.LogVerbose("Adding IHasPowerControlMessenger for {deviceKey}", device.Key);
|
||||
|
||||
var messenger = new IHasPowerMessenger(
|
||||
$"{device.Key}-powerControl-{Key}",
|
||||
@@ -681,7 +681,7 @@ namespace PepperDash.Essentials
|
||||
{
|
||||
this.LogVerbose(
|
||||
"Adding ITransportMessenger for {deviceKey}", device.Key
|
||||
);
|
||||
);
|
||||
|
||||
var messenger = new ITransportMessenger(
|
||||
$"{device.Key}-transport-{Key}",
|
||||
@@ -1619,12 +1619,12 @@ Mobile Control Direct Server Information:
|
||||
Tokens Defined: {0}
|
||||
Clients Connected: {1}
|
||||
",
|
||||
_directServer.UiClients.Count,
|
||||
_directServer.UiClientContexts.Count,
|
||||
_directServer.ConnectedUiClientsCount
|
||||
);
|
||||
|
||||
var clientNo = 1;
|
||||
foreach (var clientContext in _directServer.UiClients)
|
||||
foreach (var clientContext in _directServer.UiClientContexts)
|
||||
{
|
||||
var isAlive = false;
|
||||
var duration = "Not Connected";
|
||||
@@ -2238,7 +2238,7 @@ Mobile Control Direct Server Infromation:
|
||||
{
|
||||
this.LogInformation("-- Warning: Incoming message has no registered handler {type}", message.Type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var handler in handlers)
|
||||
{
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
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;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using WebSocketSharp;
|
||||
|
||||
namespace PepperDash.Essentials
|
||||
@@ -65,66 +62,4 @@ namespace PepperDash.Essentials
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 PepperDash.Core;
|
||||
using PepperDash.Core.Web.RequestHandlers;
|
||||
using PepperDash.Essentials.Core.Config;
|
||||
using PepperDash.Essentials.WebSocketServer;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace PepperDash.Essentials.WebApiHandlers
|
||||
{
|
||||
@@ -99,13 +99,13 @@ namespace PepperDash.Essentials.WebApiHandlers
|
||||
public int ServerPort => directServer.Port;
|
||||
|
||||
[JsonProperty("tokensDefined")]
|
||||
public int TokensDefined => directServer.UiClients.Count;
|
||||
public int TokensDefined => directServer.UiClientContexts.Count;
|
||||
|
||||
[JsonProperty("clientsConnected")]
|
||||
public int ClientsConnected => directServer.ConnectedUiClientsCount;
|
||||
|
||||
[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)
|
||||
{
|
||||
|
||||
@@ -90,7 +90,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
|
||||
{
|
||||
@@ -131,7 +131,7 @@ namespace PepperDash.Essentials.WebApiHandlers
|
||||
return;
|
||||
}
|
||||
|
||||
server.UiClients.Remove(request.Token);
|
||||
server.UiClientContexts.Remove(request.Token);
|
||||
|
||||
server.UpdateSecret();
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
|
||||
namespace PepperDash.Essentials.WebSocketServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the structure of the join response
|
||||
/// </summary>
|
||||
public class JoinResponse
|
||||
{
|
||||
[JsonProperty("clientId")]
|
||||
public string ClientId { get; set; }
|
||||
|
||||
[JsonProperty("roomKey")]
|
||||
public string RoomKey { get; set; }
|
||||
|
||||
[JsonProperty("systemUUid")]
|
||||
public string SystemUuid { get; set; }
|
||||
|
||||
[JsonProperty("roomUUid")]
|
||||
public string RoomUuid { get; set; }
|
||||
|
||||
[JsonProperty("config")]
|
||||
public object Config { get; set; }
|
||||
|
||||
[JsonProperty("codeExpires")]
|
||||
public DateTime CodeExpires { get; set; }
|
||||
|
||||
[JsonProperty("userCode")]
|
||||
public string UserCode { get; set; }
|
||||
|
||||
[JsonProperty("userAppUrl")]
|
||||
public string UserAppUrl { get; set; }
|
||||
|
||||
[JsonProperty("enableDebug")]
|
||||
public bool EnableDebug { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Independentsoft.Exchange;
|
||||
|
||||
namespace PepperDash.Essentials.WebSocketServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a join token with the associated properties
|
||||
/// </summary>
|
||||
public class JoinToken
|
||||
{
|
||||
public string Code { get; set; }
|
||||
|
||||
public string RoomKey { get; set; }
|
||||
|
||||
public string Uuid { get; set; }
|
||||
|
||||
public string TouchpanelKey { get; set; } = "";
|
||||
|
||||
public string Token { get; set; } = null;
|
||||
|
||||
public string Id { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,10 @@
|
||||
using Crestron.SimplSharp;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.WebScripting;
|
||||
using Newtonsoft.Json;
|
||||
using PepperDash.Core;
|
||||
@@ -9,12 +15,6 @@ using PepperDash.Essentials.Core.Web;
|
||||
using PepperDash.Essentials.RoomBridges;
|
||||
using PepperDash.Essentials.WebApiHandlers;
|
||||
using Serilog.Events;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using WebSocketSharp;
|
||||
using WebSocketSharp.Net;
|
||||
using WebSocketSharp.Server;
|
||||
@@ -24,6 +24,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
{
|
||||
public class MobileControlWebsocketServer : EssentialsDevice
|
||||
{
|
||||
private int nextClientId = 0;
|
||||
private readonly string userAppPath = Global.FilePathPrefix + "mcUserApp" + Global.DirectorySeparator;
|
||||
|
||||
private readonly string localConfigFolderName = "_local-config";
|
||||
@@ -40,7 +41,9 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
|
||||
public HttpServer Server => _server;
|
||||
|
||||
public Dictionary<string, UiClientContext> UiClients { get; private set; }
|
||||
public Dictionary<string, UiClientContext> UiClientContexts { get; private set; }
|
||||
|
||||
public Dictionary<string, UiClient> UiClients { get; private set; } = new Dictionary<string, UiClient>();
|
||||
|
||||
private readonly MobileControlSystemController _parent;
|
||||
|
||||
@@ -60,7 +63,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
|
||||
private string lanIpAddress => CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(EthernetAdapterType.EthernetLANAdapter));
|
||||
|
||||
private System.Net.IPAddress csIpAddress;
|
||||
private System.Net.IPAddress csIpAddress;
|
||||
|
||||
private System.Net.IPAddress csSubnetMask;
|
||||
|
||||
@@ -104,7 +107,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
{
|
||||
var count = 0;
|
||||
|
||||
foreach (var client in UiClients)
|
||||
foreach (var client in UiClientContexts)
|
||||
{
|
||||
if (client.Value.Client != null && client.Value.Client.Context.WebSocket.IsAlive)
|
||||
{
|
||||
@@ -122,7 +125,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
_parent = parent;
|
||||
|
||||
// Set the default port to be 50000 plus the slot number of the program
|
||||
Port = 50000 + (int)Global.ControlSystem.ProgramNumber;
|
||||
Port = 50000 + (int)Global.ControlSystem.ProgramNumber;
|
||||
|
||||
if (customPort != 0)
|
||||
{
|
||||
@@ -156,9 +159,9 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
{
|
||||
var csAdapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(EthernetAdapterType.EthernetCSAdapter);
|
||||
var csSubnetMask = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_MASK, csAdapterId);
|
||||
var csSubnetMask = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_MASK, csAdapterId);
|
||||
var csIpAddress = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, csAdapterId);
|
||||
|
||||
this.csSubnetMask = System.Net.IPAddress.Parse(csSubnetMask);
|
||||
@@ -173,7 +176,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
}
|
||||
|
||||
|
||||
UiClients = new Dictionary<string, UiClientContext>();
|
||||
UiClientContexts = new Dictionary<string, UiClientContext>();
|
||||
|
||||
//_joinTokens = new Dictionary<string, JoinToken>();
|
||||
|
||||
@@ -349,7 +352,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
if (!Directory.Exists($"{userAppPath}{localConfigFolderName}"))
|
||||
{
|
||||
Directory.CreateDirectory($"{userAppPath}{localConfigFolderName}");
|
||||
}
|
||||
}
|
||||
|
||||
using (var sw = new StreamWriter(File.Open($"{userAppPath}{localConfigFolderName}{Global.DirectorySeparator}{appConfigFileName}", FileMode.Create, FileAccess.ReadWrite)))
|
||||
{
|
||||
@@ -358,7 +361,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
|
||||
this.LogDebug("LAN Adapter ID: {lanAdapterId}", lanAdapterId);
|
||||
|
||||
var processorIp = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, lanAdapterId);
|
||||
var processorIp = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, lanAdapterId);
|
||||
|
||||
var config = GetApplicationConfig(processorIp);
|
||||
|
||||
@@ -378,7 +381,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
return;
|
||||
}
|
||||
|
||||
if(csAdapterId == -1)
|
||||
if (csAdapterId == -1)
|
||||
{
|
||||
this.LogDebug("CS LAN Adapter not found");
|
||||
return;
|
||||
@@ -389,8 +392,8 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
using (var sw = new StreamWriter(File.Open($"{userAppPath}{localConfigFolderName}{Global.DirectorySeparator}{appConfigCsFileName}", FileMode.Create, FileAccess.ReadWrite)))
|
||||
{
|
||||
// Write the CS application configuration file. Used when a request comes in for the application config from the CS
|
||||
var processorIp = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, csAdapterId);
|
||||
|
||||
var processorIp = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, csAdapterId);
|
||||
|
||||
var config = GetApplicationConfig(processorIp);
|
||||
|
||||
var contents = JsonConvert.SerializeObject(config, Formatting.Indented);
|
||||
@@ -400,7 +403,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
}
|
||||
|
||||
private MobileControlApplicationConfig GetApplicationConfig(string processorIp)
|
||||
{
|
||||
{
|
||||
try
|
||||
{
|
||||
var config = new MobileControlApplicationConfig
|
||||
@@ -430,10 +433,10 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogError(ex, "Error getting application configuration");
|
||||
this.LogError(ex, "Error getting application configuration");
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -472,45 +475,39 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Information, "Adding token: {0} for room: {1}", this, token.Key, token.Value.RoomKey);
|
||||
|
||||
if (UiClients == null)
|
||||
if (UiClientContexts == null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Warning, "UiClients is null", this);
|
||||
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)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Restored {uiClientCount} UiClients from secrets data", this, UiClients.Count);
|
||||
Debug.LogMessage(LogEventLevel.Information, "Restored {uiClientCount} UiClients from secrets data", this, UiClientContexts.Count);
|
||||
|
||||
foreach (var client in UiClients)
|
||||
foreach (var client in UiClientContexts)
|
||||
{
|
||||
var key = client.Key;
|
||||
var path = _wsPath + key;
|
||||
var roomKey = client.Value.Token.RoomKey;
|
||||
var token = client.Value.Token;
|
||||
|
||||
var bridge = _parent.GetRoomBridge(roomKey);
|
||||
|
||||
if (bridge == null)
|
||||
{
|
||||
this.LogWarning("No bridge found for room key: {0}", this, roomKey);
|
||||
continue;
|
||||
}
|
||||
|
||||
_server.AddWebSocketService(path, () =>
|
||||
{
|
||||
var c = new UiClient();
|
||||
Debug.LogMessage(LogEventLevel.Debug, "Constructing UiClient with id: {key}", this, key);
|
||||
|
||||
c.Controller = _parent;
|
||||
c.RoomKey = roomKey;
|
||||
UiClients[key].SetClient(c);
|
||||
return c;
|
||||
return InitializeUiClient(token, bridge);
|
||||
});
|
||||
|
||||
|
||||
//_server.WebSocketServices.AddService<UiClient>(path, (c) =>
|
||||
//{
|
||||
// Debug.Console(2, this, "Constructing UiClient with id: {0}", key);
|
||||
// c.Controller = _parent;
|
||||
// c.RoomKey = roomKey;
|
||||
// UiClients[key].SetClient(c);
|
||||
//});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -519,7 +516,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
Debug.LogMessage(LogEventLevel.Warning, "No secret found");
|
||||
}
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Debug, "{uiClientCount} UiClients restored from secrets data", this, UiClients.Count);
|
||||
Debug.LogMessage(LogEventLevel.Debug, "{uiClientCount} UiClients restored from secrets data", this, UiClientContexts.Count);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -543,7 +540,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
|
||||
_secret.Tokens.Clear();
|
||||
|
||||
foreach (var uiClientContext in UiClients)
|
||||
foreach (var uiClientContext in UiClientContexts)
|
||||
{
|
||||
_secret.Tokens.Add(uiClientContext.Key, uiClientContext.Value.Token);
|
||||
}
|
||||
@@ -572,7 +569,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
|
||||
var values = s.Split(' ');
|
||||
|
||||
if(values.Length < 2)
|
||||
if (values.Length < 2)
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("Invalid number of arguments. Please provide a room key and a grant code");
|
||||
return;
|
||||
@@ -636,24 +633,50 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
}
|
||||
}
|
||||
|
||||
private UiClient InitializeUiClient(JoinToken token, MobileControlBridgeBase bridge)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Constructing UiClient with id: {0}", this, token.Id);
|
||||
|
||||
var c = new UiClient(token.Id)
|
||||
{
|
||||
Controller = _parent,
|
||||
RoomKey = bridge.RoomKey
|
||||
};
|
||||
|
||||
UiClients.Add(c.ClientId, c);
|
||||
|
||||
// reset client ID in token for next use
|
||||
token.Id = null;
|
||||
|
||||
c.Context.WebSocket.OnClose += (sender, e) =>
|
||||
{
|
||||
if (UiClients.ContainsKey(c.ClientId))
|
||||
{
|
||||
UiClients.Remove(c.ClientId);
|
||||
}
|
||||
};
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
public (string, string) GenerateClientToken(MobileControlBridgeBase bridge, string touchPanelKey = "")
|
||||
{
|
||||
var key = Guid.NewGuid().ToString();
|
||||
|
||||
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;
|
||||
|
||||
_server.AddWebSocketService(path, () =>
|
||||
{
|
||||
var c = new UiClient();
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Constructing UiClient with id: {0}", this, key);
|
||||
c.Controller = _parent;
|
||||
c.RoomKey = bridge.RoomKey;
|
||||
UiClients[key].SetClient(c);
|
||||
return c;
|
||||
if (string.IsNullOrEmpty(token.Id))
|
||||
{
|
||||
token.Id = nextClientId++.ToString();
|
||||
}
|
||||
|
||||
return InitializeUiClient(token, bridge);
|
||||
});
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Information, "Added new WebSocket UiClient service at path: {path}", this, path);
|
||||
@@ -683,7 +706,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var client in UiClients)
|
||||
foreach (var client in UiClientContexts)
|
||||
{
|
||||
if (client.Value.Client != null && client.Value.Client.Context.WebSocket.IsAlive)
|
||||
{
|
||||
@@ -701,7 +724,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
}
|
||||
}
|
||||
|
||||
UiClients.Clear();
|
||||
UiClientContexts.Clear();
|
||||
|
||||
UpdateSecret();
|
||||
}
|
||||
@@ -720,9 +743,9 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -732,7 +755,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
var path = _wsPath + key;
|
||||
if (_server.RemoveWebSocketService(path))
|
||||
{
|
||||
UiClients.Remove(key);
|
||||
UiClientContexts.Remove(key);
|
||||
|
||||
UpdateSecret();
|
||||
|
||||
@@ -756,9 +779,9 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
{
|
||||
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));
|
||||
}
|
||||
@@ -768,7 +791,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
{
|
||||
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)
|
||||
{
|
||||
@@ -907,7 +930,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
this.LogVerbose("Join Room Request with token: {token}", token);
|
||||
|
||||
|
||||
if (UiClients.TryGetValue(token, out UiClientContext clientContext))
|
||||
if (UiClientContexts.TryGetValue(token, out UiClientContext clientContext))
|
||||
{
|
||||
var bridge = _parent.GetRoomBridge(clientContext.Token.RoomKey);
|
||||
|
||||
@@ -916,10 +939,12 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
res.StatusCode = 200;
|
||||
res.ContentType = "application/json";
|
||||
|
||||
clientContext.Token.Id = nextClientId++.ToString();
|
||||
|
||||
// Construct the response object
|
||||
JoinResponse jRes = new JoinResponse
|
||||
{
|
||||
ClientId = token,
|
||||
ClientId = clientContext.Token.Id,
|
||||
RoomKey = bridge.RoomKey,
|
||||
SystemUuid = _parent.SystemUuid,
|
||||
RoomUuid = _parent.SystemUuid,
|
||||
@@ -1137,12 +1162,14 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
/// <param name="message"></param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1153,129 +1180,25 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
/// <param name="message"></param>
|
||||
public void SendMessageToClient(object clientId, string message)
|
||||
{
|
||||
if (clientId == null)
|
||||
if (clientId == null || !(clientId is string))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (UiClients.TryGetValue((string)clientId, out UiClientContext clientContext))
|
||||
{
|
||||
if (clientContext.Client != null)
|
||||
{
|
||||
var socket = clientContext.Client.Context.WebSocket;
|
||||
|
||||
if (socket.IsAlive)
|
||||
{
|
||||
socket.Send(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
if (!UiClients.TryGetValue((string)clientId, out UiClient client))
|
||||
{
|
||||
this.LogWarning("Unable to find client with ID: {clientId}", clientId);
|
||||
return;
|
||||
}
|
||||
|
||||
var socket = client.Context.WebSocket;
|
||||
|
||||
if (!socket.IsAlive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
socket.Send(message);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Class to describe the server version info
|
||||
/// </summary>
|
||||
public class Version
|
||||
{
|
||||
[JsonProperty("serverVersion")]
|
||||
public string ServerVersion { get; set; }
|
||||
|
||||
[JsonProperty("serverIsRunningOnProcessorHardware")]
|
||||
public bool ServerIsRunningOnProcessorHardware { get; private set; }
|
||||
|
||||
public Version()
|
||||
{
|
||||
ServerIsRunningOnProcessorHardware = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an instance of a UiClient and the associated Token
|
||||
/// </summary>
|
||||
public class UiClientContext
|
||||
{
|
||||
public UiClient Client { get; private set; }
|
||||
public JoinToken Token { get; private set; }
|
||||
|
||||
public UiClientContext(JoinToken token)
|
||||
{
|
||||
Token = token;
|
||||
}
|
||||
|
||||
public void SetClient(UiClient client)
|
||||
{
|
||||
Client = client;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the data structure for the grant code and UiClient tokens to be stored in the secrets manager
|
||||
/// </summary>
|
||||
public class ServerTokenSecrets
|
||||
{
|
||||
public string GrantCode { get; set; }
|
||||
|
||||
public Dictionary<string, JoinToken> Tokens { get; set; }
|
||||
|
||||
public ServerTokenSecrets(string grantCode)
|
||||
{
|
||||
GrantCode = grantCode;
|
||||
Tokens = new Dictionary<string, JoinToken>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a join token with the associated properties
|
||||
/// </summary>
|
||||
public class JoinToken
|
||||
{
|
||||
public string Code { get; set; }
|
||||
|
||||
public string RoomKey { get; set; }
|
||||
|
||||
public string Uuid { get; set; }
|
||||
|
||||
public string TouchpanelKey { get; set; } = "";
|
||||
|
||||
public string Token { get; set; } = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the structure of the join response
|
||||
/// </summary>
|
||||
public class JoinResponse
|
||||
{
|
||||
[JsonProperty("clientId")]
|
||||
public string ClientId { get; set; }
|
||||
|
||||
[JsonProperty("roomKey")]
|
||||
public string RoomKey { get; set; }
|
||||
|
||||
[JsonProperty("systemUUid")]
|
||||
public string SystemUuid { get; set; }
|
||||
|
||||
[JsonProperty("roomUUid")]
|
||||
public string RoomUuid { get; set; }
|
||||
|
||||
[JsonProperty("config")]
|
||||
public object Config { get; set; }
|
||||
|
||||
[JsonProperty("codeExpires")]
|
||||
public DateTime CodeExpires { get; set; }
|
||||
|
||||
[JsonProperty("userCode")]
|
||||
public string UserCode { get; set; }
|
||||
|
||||
[JsonProperty("userAppUrl")]
|
||||
public string UserAppUrl { get; set; }
|
||||
|
||||
[JsonProperty("enableDebug")]
|
||||
public bool EnableDebug { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
||||
namespace PepperDash.Essentials.WebSocketServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the data structure for the grant code and UiClient tokens to be stored in the secrets manager
|
||||
/// </summary>
|
||||
public class ServerTokenSecrets
|
||||
{
|
||||
public string GrantCode { get; set; }
|
||||
|
||||
public Dictionary<string, JoinToken> Tokens { get; set; }
|
||||
|
||||
public ServerTokenSecrets(string grantCode)
|
||||
{
|
||||
GrantCode = grantCode;
|
||||
Tokens = new Dictionary<string, JoinToken>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Essentials.AppServer.Messengers;
|
||||
using PepperDash.Essentials.RoomBridges;
|
||||
using Serilog.Events;
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using WebSocketSharp;
|
||||
using WebSocketSharp.Server;
|
||||
using ErrorEventArgs = WebSocketSharp.ErrorEventArgs;
|
||||
|
||||
|
||||
namespace PepperDash.Essentials.WebSocketServer
|
||||
{
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the behaviour to associate with a UiClient for WebSocket communication
|
||||
/// </summary>
|
||||
@@ -22,7 +22,10 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
|
||||
public string RoomKey { get; set; }
|
||||
|
||||
private string _clientId;
|
||||
public string ClientId
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
private DateTime _connectionTime;
|
||||
|
||||
@@ -41,9 +44,9 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
}
|
||||
}
|
||||
|
||||
public UiClient()
|
||||
public UiClient(string clientId)
|
||||
{
|
||||
|
||||
ClientId = clientId;
|
||||
}
|
||||
|
||||
protected override void OnOpen()
|
||||
@@ -61,8 +64,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
return;
|
||||
}
|
||||
|
||||
var clientId = match.Groups[1].Value;
|
||||
_clientId = clientId;
|
||||
var clientId = ClientId;
|
||||
|
||||
if (Controller == null)
|
||||
{
|
||||
@@ -96,7 +98,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
|
||||
private void Bridge_UserCodeChanged(object sender, EventArgs e)
|
||||
{
|
||||
SendUserCodeToClient((MobileControlEssentialsRoomBridge)sender, _clientId);
|
||||
SendUserCodeToClient((MobileControlEssentialsRoomBridge)sender, ClientId);
|
||||
}
|
||||
|
||||
private void SendUserCodeToClient(MobileControlBridgeBase bridge, string clientId)
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace PepperDash.Essentials.WebSocketServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an instance of a UiClient and the associated Token
|
||||
/// </summary>
|
||||
public class UiClientContext
|
||||
{
|
||||
public UiClient Client { get; private set; }
|
||||
public JoinToken Token { get; private set; }
|
||||
|
||||
public UiClientContext(JoinToken token)
|
||||
{
|
||||
Token = token;
|
||||
}
|
||||
|
||||
public void SetClient(UiClient client)
|
||||
{
|
||||
Client = client;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
|
||||
namespace PepperDash.Essentials.WebSocketServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Class to describe the server version info
|
||||
/// </summary>
|
||||
public class Version
|
||||
{
|
||||
[JsonProperty("serverVersion")]
|
||||
public string ServerVersion { get; set; }
|
||||
|
||||
[JsonProperty("serverIsRunningOnProcessorHardware")]
|
||||
public bool ServerIsRunningOnProcessorHardware { get; private set; }
|
||||
|
||||
public Version()
|
||||
{
|
||||
ServerIsRunningOnProcessorHardware = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user