#if !BESTHTTP_DISABLE_SOCKETIO using System; using System.Collections.Generic; namespace BestHTTP.SocketIO { using BestHTTP; using BestHTTP.SocketIO.Events; /// /// This class represents a Socket.IO namespace. /// public sealed class Socket : ISocket { #region Public Properties /// /// The SocketManager instance that created this socket. /// public SocketManager Manager { get; private set; } /// /// The namespace that this socket is bound to. /// public string Namespace { get; private set; } /// /// Unique Id of the socket. /// public string Id { get; private set; } /// /// True if the socket is connected and open to the server. False otherwise. /// public bool IsOpen { get; private set; } /// /// While this property is True, the socket will decode the Packet's Payload data using the parent SocketManager's Encoder. You must set this property before any event subscription! Its default value is True; /// public bool AutoDecodePayload { get; set; } #endregion #region Privates /// /// A table to store acknowledgment callbacks associated to the given ids. /// private Dictionary AckCallbacks; /// /// Tha Callback table that helps this class to manage event subscription and dispatching events. /// private EventTable EventCallbacks; /// /// Cached list to spare some GC alloc. /// private List arguments = new List(); #endregion /// /// Internal constructor. /// internal Socket(string nsp, SocketManager manager) { this.Namespace = nsp; this.Manager = manager; this.IsOpen = false; this.AutoDecodePayload = true; this.EventCallbacks = new EventTable(this); } #region Socket Handling /// /// Internal function to start opening the socket. /// void ISocket.Open() { // The transport already established the connection if (Manager.State == SocketManager.States.Open) OnTransportOpen(Manager.Socket, null); else { // We want to receive a message when we are connected. Manager.Socket.Off(EventNames.Connect, OnTransportOpen); Manager.Socket.On(EventNames.Connect, OnTransportOpen); if (Manager.Options.AutoConnect && Manager.State == SocketManager.States.Initial) Manager.Open(); } } /// /// Disconnects this socket/namespace. /// public void Disconnect() { (this as ISocket).Disconnect(true); } /// /// Disconnects this socket/namespace. /// void ISocket.Disconnect(bool remove) { // Send a disconnect packet to the server if (IsOpen) { Packet packet = new Packet(TransportEventTypes.Message, SocketIOEventTypes.Disconnect, this.Namespace, string.Empty); (Manager as IManager).SendPacket(packet); // IsOpen must be false, because in the OnPacket preprocessing the packet would call this function again IsOpen = false; (this as ISocket).OnPacket(packet); } if (AckCallbacks != null) AckCallbacks.Clear(); if (remove) { EventCallbacks.Clear(); (Manager as IManager).Remove(this); } } #endregion #region Emit Implementations public Socket Emit(string eventName, params object[] args) { return Emit(eventName, null, args); } public Socket Emit(string eventName, SocketIOAckCallback callback, params object[] args) { bool blackListed = EventNames.IsBlacklisted(eventName); if (blackListed) throw new ArgumentException("Blacklisted event: " + eventName); arguments.Clear(); arguments.Add(eventName); // Find and swap any binary data(byte[]) to a placeholder string. // Server side these will be swapped back. List attachments = null; if (args != null && args.Length > 0) { int idx = 0; for (int i = 0; i < args.Length; ++i) { byte[] binData = args[i] as byte[]; if (binData != null) { if (attachments == null) attachments = new List(); Dictionary placeholderObj = new Dictionary(2); placeholderObj.Add(Packet.Placeholder, true); placeholderObj.Add("num", idx++); arguments.Add(placeholderObj); attachments.Add(binData); } else arguments.Add(args[i]); } } string payload = null; try { payload = Manager.Encoder.Encode(arguments); } catch(Exception ex) { (this as ISocket).EmitError(SocketIOErrors.Internal, "Error while encoding payload: " + ex.Message + " " + ex.StackTrace); return this; } // We don't use it further in this function, so we can clear it to not hold any unwanted reference. arguments.Clear(); if (payload == null) throw new ArgumentException("Encoding the arguments to JSON failed!"); int id = 0; if (callback != null) { id = Manager.NextAckId; if (AckCallbacks == null) AckCallbacks = new Dictionary(); AckCallbacks[id] = callback; } Packet packet = new Packet(TransportEventTypes.Message, attachments == null ? SocketIOEventTypes.Event : SocketIOEventTypes.BinaryEvent, this.Namespace, payload, 0, id); if (attachments != null) packet.Attachments = attachments; // This will set the AttachmentCount property too. (Manager as IManager).SendPacket(packet); return this; } public Socket EmitAck(Packet originalPacket, params object[] args) { if (originalPacket == null) throw new ArgumentNullException("originalPacket == null!"); if (/*originalPacket.Id == 0 ||*/ (originalPacket.SocketIOEvent != SocketIOEventTypes.Event && originalPacket.SocketIOEvent != SocketIOEventTypes.BinaryEvent)) throw new ArgumentException("Wrong packet - you can't send an Ack for a packet with id == 0 and SocketIOEvent != Event or SocketIOEvent != BinaryEvent!"); arguments.Clear(); if (args != null && args.Length > 0) arguments.AddRange(args); string payload = null; try { payload = Manager.Encoder.Encode(arguments); } catch (Exception ex) { (this as ISocket).EmitError(SocketIOErrors.Internal, "Error while encoding payload: " + ex.Message + " " + ex.StackTrace); return this; } if (payload == null) throw new ArgumentException("Encoding the arguments to JSON failed!"); Packet packet = new Packet(TransportEventTypes.Message, originalPacket.SocketIOEvent == SocketIOEventTypes.Event ? SocketIOEventTypes.Ack : SocketIOEventTypes.BinaryAck, this.Namespace, payload, 0, originalPacket.Id); (Manager as IManager).SendPacket(packet); return this; } #endregion #region On Implementations /// /// Register a Callback for a given name /// public void On(string eventName, SocketIOCallback callback) { EventCallbacks.Register(eventName, callback, false, this.AutoDecodePayload); } public void On(SocketIOEventTypes type, SocketIOCallback callback) { string eventName = EventNames.GetNameFor(type); EventCallbacks.Register(eventName, callback, false, this.AutoDecodePayload); } public void On(string eventName, SocketIOCallback callback, bool autoDecodePayload) { EventCallbacks.Register(eventName, callback, false, autoDecodePayload); } public void On(SocketIOEventTypes type, SocketIOCallback callback, bool autoDecodePayload) { string eventName = EventNames.GetNameFor(type); EventCallbacks.Register(eventName, callback, false, autoDecodePayload); } #endregion #region Once Implementations public void Once(string eventName, SocketIOCallback callback) { EventCallbacks.Register(eventName, callback, true, this.AutoDecodePayload); } public void Once(SocketIOEventTypes type, SocketIOCallback callback) { EventCallbacks.Register(EventNames.GetNameFor(type), callback, true, this.AutoDecodePayload); } public void Once(string eventName, SocketIOCallback callback, bool autoDecodePayload) { EventCallbacks.Register(eventName, callback, true, autoDecodePayload); } public void Once(SocketIOEventTypes type, SocketIOCallback callback, bool autoDecodePayload) { EventCallbacks.Register(EventNames.GetNameFor(type), callback, true, autoDecodePayload); } #endregion #region Off Implementations /// /// Remove all callbacks for all events. /// public void Off() { EventCallbacks.Clear(); } /// /// Removes all callbacks to the given event. /// public void Off(string eventName) { EventCallbacks.Unregister(eventName); } /// /// Removes all callbacks to the given event. /// public void Off(SocketIOEventTypes type) { Off(EventNames.GetNameFor(type)); } /// /// Remove the specified Callback. /// public void Off(string eventName, SocketIOCallback callback) { EventCallbacks.Unregister(eventName, callback); } /// /// Remove the specified Callback. /// public void Off(SocketIOEventTypes type, SocketIOCallback callback) { EventCallbacks.Unregister(EventNames.GetNameFor(type), callback); } #endregion #region Packet Handling /// /// Last call of the OnPacket chain(Transport -> Manager -> Socket), we will dispatch the event if there is any Callback /// void ISocket.OnPacket(Packet packet) { // Some preprocessing of the packet switch(packet.SocketIOEvent) { case SocketIOEventTypes.Connect: this.Id = this.Namespace != "/" ? this.Namespace + "#" + this.Manager.Handshake.Sid : this.Manager.Handshake.Sid; break; case SocketIOEventTypes.Disconnect: if (IsOpen) { IsOpen = false; EventCallbacks.Call(EventNames.GetNameFor(SocketIOEventTypes.Disconnect), packet); Disconnect(); } break; // Create an Error object from the server-sent json string case SocketIOEventTypes.Error: bool success = false; object result = JSON.Json.Decode(packet.Payload, ref success); if (success) { var errDict = result as Dictionary; Error err; if (errDict != null && errDict.ContainsKey("code")) err = new Error((SocketIOErrors)Convert.ToInt32(errDict["code"]), errDict["message"] as string); else err = new Error(SocketIOErrors.Custom, packet.Payload); EventCallbacks.Call(EventNames.GetNameFor(SocketIOEventTypes.Error), packet, err); return; } break; } // Dispatch the event to all subscriber EventCallbacks.Call(packet); // call Ack callbacks if ((packet.SocketIOEvent == SocketIOEventTypes.Ack || packet.SocketIOEvent == SocketIOEventTypes.BinaryAck) && AckCallbacks != null) { SocketIOAckCallback ackCallback = null; if (AckCallbacks.TryGetValue(packet.Id, out ackCallback) && ackCallback != null) { try { ackCallback(this, packet, this.AutoDecodePayload ? packet.Decode(Manager.Encoder) : null); } catch (Exception ex) { HTTPManager.Logger.Exception("Socket", "ackCallback", ex); } } AckCallbacks.Remove(packet.Id); } } #endregion /// /// Emits an internal packet-less event to the user level. /// void ISocket.EmitEvent(SocketIOEventTypes type, params object[] args) { (this as ISocket).EmitEvent(EventNames.GetNameFor(type), args); } /// /// Emits an internal packet-less event to the user level. /// void ISocket.EmitEvent(string eventName, params object[] args) { if (!string.IsNullOrEmpty(eventName)) EventCallbacks.Call(eventName, null, args); } void ISocket.EmitError(SocketIOErrors errCode, string msg) { (this as ISocket).EmitEvent(SocketIOEventTypes.Error, new Error(errCode, msg)); } #region Private Helper Functions /// /// Called when a "connect" event received to the root namespace /// private void OnTransportOpen(Socket socket, Packet packet, params object[] args) { // If this is not the root namespace, then we send a connect message to the server if (this.Namespace != "/") (Manager as IManager).SendPacket(new Packet(TransportEventTypes.Message, SocketIOEventTypes.Connect, this.Namespace, string.Empty)); // and we are now open IsOpen = true; } #endregion } } #endif