using System; using System.Threading.Tasks; using System.Text; using System.Collections.Generic; using System.Drawing; using IotWeb.Common.Http; using SBRL.Utilities; using Nibriboard.Client.Messages; using Newtonsoft.Json; using System.Reflection; using RippleSpace; namespace Nibriboard.Client { public class NibriClient { private static int nextId = 1; private static int getNextId() { return nextId++; } /// /// This client's unique id. /// public readonly int Id = getNextId(); /// /// The nibri client manager /// private readonly NibriClientManager manager; /// /// The underlying websocket connection to the client. /// Please try not to call the send method on here - use the NibriClient Send() method instead. /// private readonly WebSocket client; private static readonly Dictionary messageEventTypes = new Dictionary() { ["handshakeRequest"] = typeof(HandshakeRequestMessage) }; /// /// Whether this client has completed the handshake yet or not. /// public bool HandshakeCompleted = false; /// /// The name this client has assignedto themselves. /// /// The name. public string Name { get; private set; } /// /// The current area that this client is looking at. /// /// The current view port. public Rectangle CurrentViewPort { get; private set; } = Rectangle.Empty; /// /// The absolute position in plane-space of this client's cursor. /// /// The absolute cursor position. public Point AbsoluteCursorPosition { get; private set; } = Point.Empty; #region Core Setup & Message Routing Logic public NibriClient(NibriClientManager inManager, WebSocket inClient) { manager = inManager; client = inClient; client.DataReceived += async (WebSocket clientSocket, string frame) => { try { await handleMessage(frame); } catch (Exception error) { await Console.Error.WriteLineAsync(error.ToString()); throw; } //Task.Run(async () => await onMessage(frame)).Wait(); }; } private async Task handleMessage(string frame) { string eventName = JsonUtilities.DeserializeProperty(frame, "event"); if (!messageEventTypes.ContainsKey(eventName)) Log.WriteLine("Received message with invalid event {1} from Client #{0}", Id, eventName); Type messageType = messageEventTypes[eventName]; Type jsonNet = typeof(JsonConvert); MethodInfo deserialiserInfo = jsonNet.GetMethod("DeserailizeObject"); MethodInfo genericInfo = deserialiserInfo.MakeGenericMethod(messageType); var decodedMessage = genericInfo.Invoke(null, new object[] { frame }); string handlerMethodName = "handle" + decodedMessage.GetType().Name; Type clientType = this.GetType(); MethodInfo handlerInfo = clientType.GetMethod(handlerMethodName); await (Task)handlerInfo.Invoke(this, new object[] { decodedMessage }); } #endregion /// /// Sends a to the client. /// If you *really* need to send a raw message to the client, you can do so with the SendRawa() method. /// /// The message to send. public void Send(Message message) { SendRaw(JsonConvert.SerializeObject(message)); } /// /// Sends a raw string to the client. Don't use unnless you know what you're doing! /// Use the regular Send() method if you can possibly help it. /// /// The message to send. public void SendRaw(string message) { client.Send(Encoding.UTF8.GetBytes(message)); } /// /// Generates a new ClientState object representing this client's state at the current time. /// public ClientState GenerateStateSnapshot() { ClientState result = new ClientState(); result.Id = Id; result.Name = Name; result.AbsCursorPosition = AbsoluteCursorPosition; result.Viewport = CurrentViewPort; return result; } #region Message Handlers /// /// Handles an incoming handshake request. We should only receive one of these! /// protected Task handleHandshakeRequestMessage(HandshakeRequestMessage message) { CurrentViewPort = message.InitialViewport; AbsoluteCursorPosition = message.InitialAbsCursorPosition; Send(GenerateClientStateUpdate()); return Task.CompletedTask; } /// /// Handles an incoming cursor position message from the client.. /// protected Task handleCursorPositionMessage(CursorPositionMessage message) { AbsoluteCursorPosition = message.AbsCursorPosition; return Task.CompletedTask; } #endregion /// /// Generates an update message that contains information about the locations and states of all connected clients. /// Automatically omits information about the current client. /// /// The client state update message. protected ClientStateMessage GenerateClientStateUpdate() { ClientStateMessage result = new ClientStateMessage(); foreach (NibriClient client in manager.Clients) { // Don't include ourselves in the update message! if (client == this) continue; result.ClientStates.Add(client.GenerateStateSnapshot()); } return result; } } }