using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PepperDash.Core;
using PepperDash.Core.Logging;
using PepperDash.Essentials.AppServer.Messengers;
using PepperDash.Essentials.RoomBridges;
using Serilog.Events;
using WebSocketSharp;
using WebSocketSharp.Server;
using ErrorEventArgs = WebSocketSharp.ErrorEventArgs;
namespace PepperDash.Essentials.WebSocketServer
{
///
/// Represents the behaviour to associate with a UiClient for WebSocket communication
///
public class UiClient : WebSocketBehavior, IKeyed
{
///
public string Key { get; private set; }
///
/// Client ID used by client for this connection
///
public string Id { get; private set; }
///
/// Token associated with this client
///
public string Token { get; private set; }
///
/// Gets or sets the mobile control system controller that handles this client's messages
///
public MobileControlSystemController Controller { get; set; }
///
/// Gets or sets the room key that this client is associated with
///
public string RoomKey { get; set; }
///
/// The timestamp when this client connection was established
///
private DateTime _connectionTime;
///
/// Gets the duration that this client has been connected. Returns zero if not currently connected.
///
public TimeSpan ConnectedDuration
{
get
{
if (Context.WebSocket.IsAlive)
{
return DateTime.Now - _connectionTime;
}
else
{
return new TimeSpan(0);
}
}
}
///
/// Triggered when this client closes it's connection
///
public event EventHandler ConnectionClosed;
///
/// Initializes a new instance of the UiClient class with the specified key
///
/// The unique key to identify this client
/// The client ID used by the client for this connection
///
public UiClient(string key, string id, string token)
{
Key = key;
Id = id;
Token = token;
}
///
protected override void OnOpen()
{
base.OnOpen();
_connectionTime = DateTime.Now;
Log.Output = (data, message) => Utilities.ConvertWebsocketLog(data, message);
Log.Level = LogLevel.Trace;
if (Controller == null)
{
Debug.LogMessage(LogEventLevel.Verbose, "WebSocket UiClient Controller is null");
_connectionTime = DateTime.Now;
}
var clientJoinedMessage = new MobileControlMessage
{
Type = "/system/clientJoined",
Content = JToken.FromObject(new
{
clientId = Id,
roomKey = RoomKey,
})
};
Controller.HandleClientMessage(JsonConvert.SerializeObject(clientJoinedMessage));
var bridge = Controller.GetRoomBridge(RoomKey);
if (bridge == null) return;
SendUserCodeToClient(bridge, Id);
bridge.UserCodeChanged -= Bridge_UserCodeChanged;
bridge.UserCodeChanged += Bridge_UserCodeChanged;
// TODO: Future: Check token to see if there's already an open session using that token and reject/close the session
}
///
/// Handles the UserCodeChanged event from a room bridge and sends the updated user code to the client
///
/// The room bridge that raised the event
/// Event arguments
private void Bridge_UserCodeChanged(object sender, EventArgs e)
{
SendUserCodeToClient((MobileControlEssentialsRoomBridge)sender, Id);
}
///
/// Sends the current user code and QR code URL to the specified client
///
/// The room bridge containing the user code information
/// The ID of the client to send the information to
private void SendUserCodeToClient(MobileControlBridgeBase bridge, string clientId)
{
var content = new
{
userCode = bridge.UserCode,
qrUrl = bridge.QrCodeUrl,
};
var message = new MobileControlMessage
{
Type = "/system/userCodeChanged",
ClientId = clientId,
Content = JToken.FromObject(content)
};
Controller.SendMessageObjectToDirectClient(message);
}
///
protected override void OnMessage(MessageEventArgs e)
{
base.OnMessage(e);
if (e.IsText && e.Data.Length > 0 && Controller != null)
{
// Forward the message to the controller to be put on the receive queue
Controller.HandleClientMessage(e.Data);
}
}
///
protected override void OnClose(CloseEventArgs e)
{
base.OnClose(e);
this.LogInformation("WebSocket UiClient Closing: {code} reason: {reason}", e.Code, e.Reason);
foreach (var messenger in Controller.Messengers)
{
messenger.Value.UnsubscribeClient(Id);
}
foreach (var messenger in Controller.DefaultMessengers)
{
messenger.Value.UnsubscribeClient(Id);
}
ConnectionClosed?.Invoke(this, new ConnectionClosedEventArgs(Id));
}
///
protected override void OnError(ErrorEventArgs e)
{
base.OnError(e);
this.LogError("WebSocket UiClient Error: {message}", e.Message);
this.LogDebug(e.Exception, "Stack Trace");
}
}
}