diff --git a/Ooui/Client.js b/Ooui/Client.js index 6dc56cb..1841c8f 100644 --- a/Ooui/Client.js +++ b/Ooui/Client.js @@ -12,5 +12,5 @@ socket.addEventListener('open', function (event) { // Listen for messages socket.addEventListener('message', function (event) { - console.log('Message from server', event.data); + console.log('Message from server', JSON.parse (event.data)); }); diff --git a/Ooui/Message.cs b/Ooui/Message.cs index 4d8f00f..56a0614 100644 --- a/Ooui/Message.cs +++ b/Ooui/Message.cs @@ -1,13 +1,26 @@ using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; namespace Ooui { public class Message { + [JsonProperty("t")] + [JsonConverter (typeof (MillisecondEpochConverter))] public DateTime CreatedTime = DateTime.UtcNow; + + [JsonProperty("m")] + [JsonConverter (typeof (StringEnumConverter))] public MessageType MessageType = MessageType.Nop; + + [JsonProperty("id")] public string TargetId = ""; - public string Member = ""; + + [JsonProperty("k")] + public string Key = ""; + + [JsonProperty("v")] public string Value = ""; public void SetValue (object value) @@ -20,7 +33,7 @@ namespace Ooui Value = EncodeString (s); break; default: - Value = String.Format (System.Globalization.CultureInfo.InvariantCulture, "{0}", value); + Value = JsonConvert.SerializeObject (value); break; } } @@ -42,4 +55,20 @@ namespace Ooui Set, Call, } + + class MillisecondEpochConverter : DateTimeConverterBase + { + private static readonly DateTime epoch = new DateTime (1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + public override void WriteJson (JsonWriter writer, object value, JsonSerializer serializer) + { + writer.WriteRawValue (((DateTime)value - epoch).TotalMilliseconds.ToString (System.Globalization.CultureInfo.InvariantCulture)); + } + + public override object ReadJson (JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.Value == null) return null; + return epoch.AddMilliseconds ((double)reader.Value); + } + } } diff --git a/Ooui/Node.cs b/Ooui/Node.cs index 45b64b3..5b858f2 100644 --- a/Ooui/Node.cs +++ b/Ooui/Node.cs @@ -14,6 +14,13 @@ namespace Ooui readonly List messages = new List (); readonly List stateMessages = new List (); + public event Action MessageLogged; + + public IEnumerable AllMessages => + messages + .Concat (from c in children from m in c.AllMessages select m) + .OrderBy (x => x.CreatedTime); + public Node () { Mapping = Mapping.Get (GetType ()); @@ -66,7 +73,7 @@ namespace Ooui { var old = stateMessages.FirstOrDefault ( x => x.MessageType == MessageType.Set && - x.Member == message.Member); + x.Key == message.Key); if (old != null) { stateMessages.Remove (old); } @@ -74,6 +81,8 @@ namespace Ooui } break; } + + MessageLogged?.Invoke (message); } protected void LogCreate () @@ -90,7 +99,7 @@ namespace Ooui Log (new Message { MessageType = MessageType.Call, TargetId = Id, - Member = methodName, + Key = methodName, }); } @@ -99,13 +108,13 @@ namespace Ooui var m = new Message { MessageType = MessageType.Set, TargetId = Id, - Member = Mapping.GetMemberPath (propertyName), + Key = Mapping.GetMemberPath (propertyName), }; m.SetValue (value); Log (m); } - protected bool SetProperty (ref T backingStore, T newValue, string propertyName = "") + protected bool SetProperty (ref T backingStore, T newValue, [System.Runtime.CompilerServices.CallerMemberName] string propertyName = "") { if (!backingStore.Equals (newValue)) { backingStore = newValue; diff --git a/Ooui/Server.cs b/Ooui/Server.cs index c581d61..70904dc 100644 --- a/Ooui/Server.cs +++ b/Ooui/Server.cs @@ -129,6 +129,9 @@ namespace Ooui async void ProcessWebSocketRequest (HttpListenerContext listenerContext, CancellationToken token) { + // + // Find the element + // var url = listenerContext.Request.Url; var path = url.LocalPath; @@ -150,9 +153,14 @@ namespace Ooui return; } + // + // Connect the web socket + // WebSocketContext webSocketContext = null; + WebSocket webSocket = null; try { webSocketContext = await listenerContext.AcceptWebSocketAsync(subProtocol: "ooui-1.0").ConfigureAwait (false); + webSocket = webSocketContext.WebSocket; Console.WriteLine ("WEBSOCKET {0}", listenerContext.Request.Url.LocalPath); } catch (Exception ex) { @@ -162,9 +170,30 @@ namespace Ooui return; } - WebSocket webSocket = null; + + // + // Preparse handlers for the element + // + Action onElementMessage = async m => { + if (webSocket == null) return; + try { + await SendMessageAsync (webSocket, m, token); + } + catch (Exception ex) { + Error ("Failed to handled element message", ex); + } + }; + + // + // Communicate! + // try { - webSocket = webSocketContext.WebSocket; + foreach (var m in element.AllMessages) { + if (webSocket.State == WebSocketState.Open) { + await SendMessageAsync (webSocket, m, token); + } + } + element.MessageLogged += onElementMessage; var receiveBuffer = new byte[1024]; @@ -190,8 +219,8 @@ namespace Ooui var receivedString = Encoding.UTF8.GetString (receiveBuffer, 0, size); Console.WriteLine ("RECEIVED: {0}", receivedString); - var outputBuffer = new ArraySegment (Encoding.UTF8.GetBytes ($"You said: {receivedString}")); - await webSocket.SendAsync (outputBuffer, WebSocketMessageType.Text, true, token).ConfigureAwait (false); + // var outputBuffer = new ArraySegment (Encoding.UTF8.GetBytes ($"You said: {receivedString}")); + // await webSocket.SendAsync (outputBuffer, WebSocketMessageType.Text, true, token).ConfigureAwait (false); } } } @@ -202,10 +231,18 @@ namespace Ooui Error ("Failed to process web socket", ex); } finally { + element.MessageLogged -= onElementMessage; webSocket?.Dispose (); } } + Task SendMessageAsync (WebSocket webSocket, Message message, CancellationToken token) + { + var json = Newtonsoft.Json.JsonConvert.SerializeObject (message); + var outputBuffer = new ArraySegment (Encoding.UTF8.GetBytes (json)); + return webSocket.SendAsync (outputBuffer, WebSocketMessageType.Text, true, token); + } + void Error (string message, Exception ex) { Console.ForegroundColor = ConsoleColor.Red; diff --git a/Samples/Program.cs b/Samples/Program.cs index 6a5cab7..cc0d114 100644 --- a/Samples/Program.cs +++ b/Samples/Program.cs @@ -9,6 +9,7 @@ namespace Samples { var server = new Server (); var button = new Button(); + button.Name = "TestButton"; server.Publish ("/button", button); server.RunAsync ("http://*:8080/").Wait (); return 0;