SocketIOChatSample.cs 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. #if !BESTHTTP_DISABLE_SOCKETIO
  2. using System;
  3. using System.Collections.Generic;
  4. using UnityEngine;
  5. using BestHTTP.SocketIO;
  6. using UnityEngine.UI;
  7. using BestHTTP.Examples.Helpers;
  8. namespace BestHTTP.Examples
  9. {
  10. public sealed class SocketIOChatSample : BestHTTP.Examples.Helpers.SampleBase
  11. {
  12. private readonly TimeSpan TYPING_TIMER_LENGTH = TimeSpan.FromMilliseconds(700);
  13. #pragma warning disable 0649, 0414
  14. [SerializeField]
  15. [Tooltip("The WebSocket address to connect")]
  16. private string address = "https://socketio-chat-h9jt.herokuapp.com/socket.io/";
  17. [Header("Login Details")]
  18. [SerializeField]
  19. private RectTransform _loginRoot;
  20. [SerializeField]
  21. private InputField _userNameInput;
  22. [Header("Chat Setup")]
  23. [SerializeField]
  24. private RectTransform _chatRoot;
  25. [SerializeField]
  26. private Text _participantsText;
  27. [SerializeField]
  28. private ScrollRect _scrollRect;
  29. [SerializeField]
  30. private RectTransform _contentRoot;
  31. [SerializeField]
  32. private TextListItem _listItemPrefab;
  33. [SerializeField]
  34. private int _maxListItemEntries = 100;
  35. [SerializeField]
  36. private Text _typingUsersText;
  37. [SerializeField]
  38. private InputField _input;
  39. [Header("Buttons")]
  40. [SerializeField]
  41. private Button _connectButton;
  42. [SerializeField]
  43. private Button _closeButton;
  44. #pragma warning restore
  45. /// <summary>
  46. /// The Socket.IO manager instance.
  47. /// </summary>
  48. private SocketManager Manager;
  49. /// <summary>
  50. /// True if the user is currently typing
  51. /// </summary>
  52. private bool typing;
  53. /// <summary>
  54. /// When the message changed.
  55. /// </summary>
  56. private DateTime lastTypingTime = DateTime.MinValue;
  57. /// <summary>
  58. /// Users that typing.
  59. /// </summary>
  60. private List<string> typingUsers = new List<string>();
  61. #region Unity Events
  62. protected override void Start()
  63. {
  64. base.Start();
  65. this._userNameInput.text = PlayerPrefs.GetString("SocketIOChatSample_UserName");
  66. SetButtons(!string.IsNullOrEmpty(this._userNameInput.text), false);
  67. SetPanels(true);
  68. }
  69. void OnDestroy()
  70. {
  71. if (this.Manager != null)
  72. {
  73. // Leaving this sample, close the socket
  74. this.Manager.Close();
  75. this.Manager = null;
  76. }
  77. }
  78. public void OnUserNameInputChanged(string userName)
  79. {
  80. SetButtons(!string.IsNullOrEmpty(userName), false);
  81. }
  82. public void OnUserNameInputSubmit(string userName)
  83. {
  84. if (Input.GetKeyDown(KeyCode.KeypadEnter) || Input.GetKeyDown(KeyCode.Return))
  85. OnConnectButton();
  86. }
  87. public void OnConnectButton()
  88. {
  89. SetPanels(false);
  90. PlayerPrefs.SetString("SocketIOChatSample_UserName", this._userNameInput.text);
  91. AddText("Connecting...");
  92. // Create the Socket.IO manager
  93. Manager = new SocketManager(new Uri(address));
  94. Manager.Socket.On(SocketIOEventTypes.Connect, (s, p, a) =>
  95. {
  96. AddText("Connected!");
  97. Manager.Socket.Emit("add user", this._userNameInput.text);
  98. this._input.interactable = true;
  99. });
  100. Manager.Socket.On(SocketIOEventTypes.Disconnect, (s, p, a) =>
  101. {
  102. AddText("Disconnected!");
  103. SetPanels(true);
  104. SetButtons(true, false);
  105. });
  106. // Set up custom chat events
  107. Manager.Socket.On("login", OnLogin);
  108. Manager.Socket.On("new message", OnNewMessage);
  109. Manager.Socket.On("user joined", OnUserJoined);
  110. Manager.Socket.On("user left", OnUserLeft);
  111. Manager.Socket.On("typing", OnTyping);
  112. Manager.Socket.On("stop typing", OnStopTyping);
  113. // The argument will be an Error object.
  114. Manager.Socket.On(SocketIOEventTypes.Error, (socket, packet, args) => {
  115. Debug.LogError(string.Format("Error: {0}", args[0].ToString()));
  116. });
  117. SetButtons(false, true);
  118. }
  119. public void OnCloseButton()
  120. {
  121. SetButtons(false, false);
  122. this.Manager.Close();
  123. }
  124. void Update()
  125. {
  126. if (typing)
  127. {
  128. var typingTimer = DateTime.UtcNow;
  129. var timeDiff = typingTimer - lastTypingTime;
  130. if (timeDiff >= TYPING_TIMER_LENGTH)
  131. {
  132. Manager.Socket.Emit("stop typing");
  133. typing = false;
  134. }
  135. }
  136. }
  137. #endregion
  138. #region Chat Logic
  139. public void OnMessageInput(string textToSend)
  140. {
  141. if ((!Input.GetKeyDown(KeyCode.KeypadEnter) && !Input.GetKeyDown(KeyCode.Return)) || string.IsNullOrEmpty(textToSend))
  142. return;
  143. Manager.Socket.Emit("new message", textToSend);
  144. AddText(string.Format("{0}: {1}", this._userNameInput.text, textToSend));
  145. }
  146. public void UpdateTyping(string textToSend)
  147. {
  148. if (!typing)
  149. {
  150. typing = true;
  151. Manager.Socket.Emit("typing");
  152. }
  153. lastTypingTime = DateTime.UtcNow;
  154. }
  155. void addParticipantsMessage(Dictionary<string, object> data)
  156. {
  157. int numUsers = Convert.ToInt32(data["numUsers"]);
  158. if (numUsers == 1)
  159. this._participantsText.text = "there's 1 participant";
  160. else
  161. this._participantsText.text = "there are " + numUsers + " participants";
  162. }
  163. void addChatMessage(Dictionary<string, object> data)
  164. {
  165. var username = data["username"] as string;
  166. var msg = data["message"] as string;
  167. AddText(string.Format("{0}: {1}", username, msg));
  168. }
  169. void AddChatTyping(Dictionary<string, object> data)
  170. {
  171. var username = data["username"] as string;
  172. int idx = typingUsers.FindIndex((name) => name.Equals(username));
  173. if (idx == -1)
  174. typingUsers.Add(username);
  175. SetTypingUsers();
  176. }
  177. void RemoveChatTyping(Dictionary<string, object> data)
  178. {
  179. var username = data["username"] as string;
  180. int idx = typingUsers.FindIndex((name) => name.Equals(username));
  181. if (idx != -1)
  182. typingUsers.RemoveAt(idx);
  183. SetTypingUsers();
  184. }
  185. #endregion
  186. #region Custom SocketIO Events
  187. void OnLogin(Socket socket, Packet packet, params object[] args)
  188. {
  189. AddText("Welcome to Socket.IO Chat");
  190. addParticipantsMessage(args[0] as Dictionary<string, object>);
  191. }
  192. void OnNewMessage(Socket socket, Packet packet, params object[] args)
  193. {
  194. addChatMessage(args[0] as Dictionary<string, object>);
  195. }
  196. void OnUserJoined(Socket socket, Packet packet, params object[] args)
  197. {
  198. var data = args[0] as Dictionary<string, object>;
  199. var username = data["username"] as string;
  200. AddText(string.Format("{0} joined", username));
  201. addParticipantsMessage(data);
  202. }
  203. void OnUserLeft(Socket socket, Packet packet, params object[] args)
  204. {
  205. var data = args[0] as Dictionary<string, object>;
  206. var username = data["username"] as string;
  207. AddText(string.Format("{0} left", username));
  208. addParticipantsMessage(data);
  209. }
  210. void OnTyping(Socket socket, Packet packet, params object[] args)
  211. {
  212. AddChatTyping(args[0] as Dictionary<string, object>);
  213. }
  214. void OnStopTyping(Socket socket, Packet packet, params object[] args)
  215. {
  216. RemoveChatTyping(args[0] as Dictionary<string, object>);
  217. }
  218. #endregion
  219. private void AddText(string text)
  220. {
  221. GUIHelper.AddText(this._listItemPrefab, this._contentRoot, text, this._maxListItemEntries, this._scrollRect);
  222. }
  223. private void SetTypingUsers()
  224. {
  225. if (this.typingUsers.Count > 0)
  226. {
  227. System.Text.StringBuilder sb = new System.Text.StringBuilder(this.typingUsers[0], this.typingUsers.Count + 1);
  228. for (int i = 1; i < this.typingUsers.Count; ++i)
  229. sb.AppendFormat(", {0}", this.typingUsers[i]);
  230. if (this.typingUsers.Count == 1)
  231. sb.Append(" is typing!");
  232. else
  233. sb.Append(" are typing!");
  234. this._typingUsersText.text = sb.ToString();
  235. }
  236. else
  237. this._typingUsersText.text = string.Empty;
  238. }
  239. private void SetPanels(bool login)
  240. {
  241. if (login)
  242. {
  243. this._loginRoot.gameObject.SetActive(true);
  244. this._chatRoot.gameObject.SetActive(false);
  245. this._input.interactable = false;
  246. }
  247. else
  248. {
  249. this._loginRoot.gameObject.SetActive(false);
  250. this._chatRoot.gameObject.SetActive(true);
  251. this._input.interactable = true;
  252. }
  253. }
  254. private void SetButtons(bool connect, bool close)
  255. {
  256. if (this._connectButton != null)
  257. this._connectButton.interactable = connect;
  258. if (this._closeButton != null)
  259. this._closeButton.interactable = close;
  260. }
  261. }
  262. }
  263. #endif