mirror of
https://github.com/PepperDash/Essentials.git
synced 2026-01-11 19:44:52 +00:00
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:
90
src/PepperDash.Essentials.MobileControl/MessageToClients.cs
Normal file
90
src/PepperDash.Essentials.MobileControl/MessageToClients.cs
Normal 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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1382,33 +1382,13 @@ namespace PepperDash.Essentials
|
||||
{
|
||||
Log =
|
||||
{
|
||||
Output = (data, message) =>
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
Output = (data, message) => Utilities.ConvertWebsocketLog(data, message, this)
|
||||
}
|
||||
};
|
||||
|
||||
// setting to trace to let level be controlled by appdebug
|
||||
_wsClient2.Log.Level = LogLevel.Trace;
|
||||
|
||||
_wsClient2.SslConfiguration.EnabledSslProtocols =
|
||||
System.Security.Authentication.SslProtocols.Tls11
|
||||
| System.Security.Authentication.SslProtocols.Tls12;
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
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
|
||||
@@ -20,12 +16,22 @@ namespace PepperDash.Essentials
|
||||
private readonly WebSocket _ws;
|
||||
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)
|
||||
{
|
||||
_ws = ws;
|
||||
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)
|
||||
{
|
||||
_ws = ws;
|
||||
@@ -43,13 +49,13 @@ namespace PepperDash.Essentials
|
||||
{
|
||||
if (_ws == null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Warning, "Cannot send message. Websocket client is null");
|
||||
Debug.LogWarning("Cannot send message. Websocket client is null");
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -57,83 +63,14 @@ namespace PepperDash.Essentials
|
||||
var message = JsonConvert.SerializeObject(msgToSend, Formatting.None,
|
||||
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);
|
||||
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogMessage(ex, "Caught an exception in the Transmit Processor");
|
||||
}
|
||||
}
|
||||
#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");
|
||||
Debug.LogError("Caught an exception in the Transmit Processor: {message}", ex.Message);
|
||||
Debug.LogDebug(ex, "Stack Trace: ");
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
97
src/PepperDash.Essentials.MobileControl/Utilities.cs
Normal file
97
src/PepperDash.Essentials.MobileControl/Utilities.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
@@ -111,13 +111,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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
@@ -134,7 +134,7 @@ namespace PepperDash.Essentials.WebApiHandlers
|
||||
return;
|
||||
}
|
||||
|
||||
server.UiClients.Remove(request.Token);
|
||||
server.UiClientContexts.Remove(request.Token);
|
||||
|
||||
server.UpdateSecret();
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,10 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
/// </summary>
|
||||
public class JoinToken
|
||||
{
|
||||
/// <summary>
|
||||
/// Unique client ID for a client that is joining
|
||||
/// </summary>
|
||||
public string Id { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the Code
|
||||
/// </summary>
|
||||
|
||||
@@ -10,6 +10,7 @@ using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.WebScripting;
|
||||
using Newtonsoft.Json;
|
||||
using Org.BouncyCastle.Crypto.Prng;
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Core.Logging;
|
||||
using PepperDash.Essentials.Core;
|
||||
@@ -56,7 +57,9 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
/// <summary>
|
||||
/// Gets the collection of UI client contexts
|
||||
/// </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;
|
||||
|
||||
@@ -129,7 +132,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)
|
||||
{
|
||||
@@ -202,7 +205,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
}
|
||||
|
||||
|
||||
UiClients = new Dictionary<string, UiClientContext>();
|
||||
UiClientContexts = new Dictionary<string, UiClientContext>();
|
||||
|
||||
//_joinTokens = new Dictionary<string, JoinToken>();
|
||||
|
||||
@@ -278,29 +281,20 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
}
|
||||
|
||||
_server.Log.Output = (data, message) =>
|
||||
{
|
||||
switch (data.Level)
|
||||
{
|
||||
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;
|
||||
}
|
||||
};
|
||||
case LogLevel.Trace: this.LogVerbose(message); break;
|
||||
case LogLevel.Debug: this.LogDebug(message); break;
|
||||
case LogLevel.Info: this.LogInformation(message); break;
|
||||
case LogLevel.Warn: this.LogWarning(message); break;
|
||||
case LogLevel.Error: this.LogError(message); break;
|
||||
case LogLevel.Fatal: this.LogFatal(message); break;
|
||||
}
|
||||
};
|
||||
|
||||
// setting to trace to allow logging level to be controlled by appdebug
|
||||
_server.Log.Level = LogLevel.Trace;
|
||||
|
||||
CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler;
|
||||
|
||||
@@ -554,20 +548,20 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
|
||||
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 path = _wsPath + key;
|
||||
@@ -575,13 +569,8 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
|
||||
_server.AddWebSocketService(path, () =>
|
||||
{
|
||||
var c = new UiClient($"uiclient-{key}-{roomKey}");
|
||||
this.LogDebug("Constructing UiClient with id: {key}", key);
|
||||
|
||||
c.Controller = _parent;
|
||||
c.RoomKey = roomKey;
|
||||
UiClients[key].SetClient(c);
|
||||
return c;
|
||||
this.LogInformation("Building a UiClient with ID {id}", client.Value.Token.Id);
|
||||
return BuildUiClient(roomKey, client.Value.Token, key);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -591,7 +580,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
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)
|
||||
{
|
||||
@@ -616,7 +605,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);
|
||||
}
|
||||
@@ -725,21 +714,17 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
|
||||
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($"uiclient-{key}-{bridge.RoomKey}");
|
||||
this.LogVerbose("Constructing UiClient with id: {key}", key);
|
||||
c.Controller = _parent;
|
||||
c.RoomKey = bridge.RoomKey;
|
||||
UiClients[key].SetClient(c);
|
||||
return c;
|
||||
this.LogInformation("Building a UiClient with ID {id}", token.Id);
|
||||
return BuildUiClient(bridge.RoomKey, token, key);
|
||||
});
|
||||
|
||||
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.LogVerbose("{serviceCount} websocket services present", _server.WebSocketServices.Count);
|
||||
@@ -749,6 +734,44 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
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>
|
||||
/// Removes all clients from the server
|
||||
/// </summary>
|
||||
@@ -766,7 +789,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)
|
||||
{
|
||||
@@ -784,7 +807,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
}
|
||||
}
|
||||
|
||||
UiClients.Clear();
|
||||
UiClientContexts.Clear();
|
||||
|
||||
UpdateSecret();
|
||||
}
|
||||
@@ -803,9 +826,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)
|
||||
{
|
||||
@@ -815,7 +838,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
var path = _wsPath + key;
|
||||
if (_server.RemoveWebSocketService(path))
|
||||
{
|
||||
UiClients.Remove(key);
|
||||
UiClientContexts.Remove(key);
|
||||
|
||||
UpdateSecret();
|
||||
|
||||
@@ -839,9 +862,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));
|
||||
}
|
||||
@@ -851,7 +874,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)
|
||||
{
|
||||
@@ -990,77 +1013,81 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
|
||||
this.LogVerbose("Join Room Request with token: {token}", token);
|
||||
|
||||
byte[] body;
|
||||
|
||||
if (UiClients.TryGetValue(token, out UiClientContext clientContext))
|
||||
{
|
||||
var bridge = _parent.GetRoomBridge(clientContext.Token.RoomKey);
|
||||
|
||||
if (bridge != null)
|
||||
{
|
||||
res.StatusCode = 200;
|
||||
res.ContentType = "application/json";
|
||||
|
||||
var devices = DeviceManager.GetDevices();
|
||||
Dictionary<string, DeviceInterfaceInfo> deviceInterfaces = new Dictionary<string, DeviceInterfaceInfo>();
|
||||
|
||||
foreach (var device in devices)
|
||||
{
|
||||
var interfaces = device?.GetType().GetInterfaces().Select((i) => i.Name).ToList() ?? new List<string>();
|
||||
deviceInterfaces.Add(device.Key, new DeviceInterfaceInfo
|
||||
{
|
||||
Key = device.Key,
|
||||
Name = device is IKeyName ? (device as IKeyName).Name : "",
|
||||
Interfaces = interfaces
|
||||
});
|
||||
}
|
||||
|
||||
// Construct the response object
|
||||
JoinResponse jRes = new JoinResponse
|
||||
{
|
||||
ClientId = token,
|
||||
RoomKey = bridge.RoomKey,
|
||||
SystemUuid = _parent.SystemUuid,
|
||||
RoomUuid = _parent.SystemUuid,
|
||||
Config = _parent.GetConfigWithPluginVersion(),
|
||||
CodeExpires = new DateTime().AddYears(1),
|
||||
UserCode = bridge.UserCode,
|
||||
UserAppUrl = string.Format("http://{0}:{1}/mc/app",
|
||||
CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0),
|
||||
Port),
|
||||
EnableDebug = false,
|
||||
DeviceInterfaceSupport = deviceInterfaces
|
||||
};
|
||||
|
||||
// Serialize to JSON and convert to Byte[]
|
||||
var json = JsonConvert.SerializeObject(jRes);
|
||||
var body = Encoding.UTF8.GetBytes(json);
|
||||
res.ContentLength64 = body.LongLength;
|
||||
|
||||
// Send the response
|
||||
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
|
||||
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);
|
||||
var body = Encoding.UTF8.GetBytes(message);
|
||||
body = Encoding.UTF8.GetBytes(message);
|
||||
res.ContentLength64 = body.LongLength;
|
||||
res.Close(body, true);
|
||||
return;
|
||||
}
|
||||
|
||||
var bridge = _parent.GetRoomBridge(clientContext.Token.RoomKey);
|
||||
|
||||
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.ContentType = "application/json";
|
||||
|
||||
var devices = DeviceManager.GetDevices();
|
||||
Dictionary<string, DeviceInterfaceInfo> deviceInterfaces = new Dictionary<string, DeviceInterfaceInfo>();
|
||||
|
||||
foreach (var device in devices)
|
||||
{
|
||||
var interfaces = device?.GetType().GetInterfaces().Select((i) => i.Name).ToList() ?? new List<string>();
|
||||
|
||||
deviceInterfaces.Add(device.Key, new DeviceInterfaceInfo
|
||||
{
|
||||
Key = device.Key,
|
||||
Name = (device as IKeyName)?.Name ?? "",
|
||||
Interfaces = interfaces
|
||||
});
|
||||
}
|
||||
|
||||
var clientId = $"{Utilities.GetNextClientId()}";
|
||||
clientContext.Token.Id = clientId;
|
||||
|
||||
this.LogVerbose("Assigning ClientId: {clientId}", clientId);
|
||||
|
||||
// Construct the response object
|
||||
JoinResponse jRes = new JoinResponse
|
||||
{
|
||||
ClientId = clientId,
|
||||
RoomKey = bridge.RoomKey,
|
||||
SystemUuid = _parent.SystemUuid,
|
||||
RoomUuid = _parent.SystemUuid,
|
||||
Config = _parent.GetConfigWithPluginVersion(),
|
||||
CodeExpires = new DateTime().AddYears(1),
|
||||
UserCode = bridge.UserCode,
|
||||
UserAppUrl = string.Format("http://{0}:{1}/mc/app",
|
||||
CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0),
|
||||
Port),
|
||||
EnableDebug = false,
|
||||
DeviceInterfaceSupport = deviceInterfaces
|
||||
};
|
||||
|
||||
// Serialize to JSON and convert to Byte[]
|
||||
var json = JsonConvert.SerializeObject(jRes);
|
||||
body = Encoding.UTF8.GetBytes(json);
|
||||
res.ContentLength64 = body.LongLength;
|
||||
|
||||
// Send the response
|
||||
res.Close(body, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1242,12 +1269,14 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
/// </summary>
|
||||
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,17 +1295,16 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
return;
|
||||
}
|
||||
|
||||
if (UiClients.TryGetValue((string)clientId, out UiClientContext clientContext))
|
||||
if (uiClients.TryGetValue((string)clientId, out var client))
|
||||
{
|
||||
if (clientContext.Client != null)
|
||||
{
|
||||
var socket = clientContext.Client.Context.WebSocket;
|
||||
var socket = client.Context.WebSocket;
|
||||
|
||||
if (socket.IsAlive)
|
||||
{
|
||||
socket.Send(message);
|
||||
}
|
||||
if (!socket.IsAlive)
|
||||
{
|
||||
this.LogError("Unable to send message to client {id}. Client is disconnected: {message}", clientId, message);
|
||||
return;
|
||||
}
|
||||
socket.Send(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -64,9 +64,11 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
/// Initializes a new instance of the UiClient class with the specified key
|
||||
/// </summary>
|
||||
/// <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;
|
||||
Id = id;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -74,19 +76,33 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
{
|
||||
base.OnOpen();
|
||||
|
||||
var url = Context.WebSocket.Url;
|
||||
this.LogInformation("New WebSocket Connection from: {url}", url);
|
||||
Log.Output = (data, message) => Utilities.ConvertWebsocketLog(data, message);
|
||||
Log.Level = LogLevel.Trace;
|
||||
|
||||
var match = Regex.Match(url.AbsoluteUri, "(?:ws|wss):\\/\\/.*(?:\\/mc\\/api\\/ui\\/join\\/)(.*)");
|
||||
|
||||
if (!match.Success)
|
||||
try
|
||||
{
|
||||
_connectionTime = DateTime.Now;
|
||||
return;
|
||||
this.LogDebug("Current session count on open {count}", Sessions.Count);
|
||||
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;
|
||||
_clientId = clientId;
|
||||
// var url = Context.WebSocket.Url;
|
||||
// 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)
|
||||
{
|
||||
@@ -99,7 +115,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
Type = "/system/clientJoined",
|
||||
Content = JToken.FromObject(new
|
||||
{
|
||||
clientId,
|
||||
clientId = Id,
|
||||
roomKey = RoomKey,
|
||||
})
|
||||
};
|
||||
@@ -110,7 +126,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
|
||||
if (bridge == null) return;
|
||||
|
||||
SendUserCodeToClient(bridge, clientId);
|
||||
SendUserCodeToClient(bridge, Id);
|
||||
|
||||
bridge.UserCodeChanged -= Bridge_UserCodeChanged;
|
||||
bridge.UserCodeChanged += Bridge_UserCodeChanged;
|
||||
@@ -168,17 +184,30 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
{
|
||||
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);
|
||||
|
||||
foreach (var messenger in Controller.Messengers)
|
||||
{
|
||||
messenger.Value.UnsubscribeClient(_clientId);
|
||||
messenger.Value.UnsubscribeClient(Id);
|
||||
}
|
||||
|
||||
foreach (var messenger in Controller.DefaultMessengers)
|
||||
{
|
||||
messenger.Value.UnsubscribeClient(_clientId);
|
||||
messenger.Value.UnsubscribeClient(Id);
|
||||
}
|
||||
|
||||
ConnectionClosed?.Invoke(this, new ConnectionClosedEventArgs(Id));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
Reference in New Issue
Block a user