ChatClient.cs 85 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828
  1. // ----------------------------------------------------------------------------------------------------------------------
  2. // <summary>The Photon Chat Api enables clients to connect to a chat server and communicate with other clients.</summary>
  3. // <remarks>ChatClient is the main class of this api.</remarks>
  4. // <copyright company="Exit Games GmbH">Photon Chat Api - Copyright (C) 2014 Exit Games GmbH</copyright>
  5. // ----------------------------------------------------------------------------------------------------------------------
  6. #if UNITY_4_7 || UNITY_5 || UNITY_5_3_OR_NEWER
  7. #define SUPPORTED_UNITY
  8. #endif
  9. namespace Photon.Chat
  10. {
  11. using System;
  12. using System.Collections.Generic;
  13. using System.Diagnostics;
  14. using ExitGames.Client.Photon;
  15. #if SUPPORTED_UNITY || NETFX_CORE
  16. using Hashtable = ExitGames.Client.Photon.Hashtable;
  17. using SupportClass = ExitGames.Client.Photon.SupportClass;
  18. #endif
  19. /// <summary>Central class of the Photon Chat API to connect, handle channels and messages.</summary>
  20. /// <remarks>
  21. /// This class must be instantiated with a IChatClientListener instance to get the callbacks.
  22. /// Integrate it into your game loop by calling Service regularly. If the target platform supports Threads/Tasks,
  23. /// set UseBackgroundWorkerForSending = true, to let the ChatClient keep the connection by sending from
  24. /// an independent thread.
  25. ///
  26. /// Call Connect with an AppId that is setup as Photon Chat application. Note: Connect covers multiple
  27. /// messages between this client and the servers. A short workflow will connect you to a chat server.
  28. ///
  29. /// Each ChatClient resembles a user in chat (set in Connect). Each user automatically subscribes a channel
  30. /// for incoming private messages and can message any other user privately.
  31. /// Before you publish messages in any non-private channel, that channel must be subscribed.
  32. ///
  33. /// PublicChannels is a list of subscribed channels, containing messages and senders.
  34. /// PrivateChannels contains all incoming and sent private messages.
  35. /// </remarks>
  36. public class ChatClient : IPhotonPeerListener
  37. {
  38. const int FriendRequestListMax = 1024;
  39. /// <summary> Default maximum value possible for <see cref="ChatChannel.MaxSubscribers"/> when <see cref="ChatChannel.PublishSubscribers"/> is enabled</summary>
  40. public const int DefaultMaxSubscribers = 100;
  41. private const byte HttpForwardWebFlag = 0x01;
  42. /// <summary>Enables a fallback to another protocol in case a connect to the Name Server fails.</summary>
  43. /// <remarks>
  44. /// When connecting to the Name Server fails for a first time, the client will select an alternative
  45. /// network protocol and re-try to connect.
  46. ///
  47. /// The fallback will use the default Name Server port as defined by ProtocolToNameServerPort.
  48. ///
  49. /// The fallback for TCP is UDP. All other protocols fallback to TCP.
  50. /// </remarks>
  51. public bool EnableProtocolFallback { get; set; }
  52. /// <summary>The address of last connected Name Server.</summary>
  53. public string NameServerAddress { get; private set; }
  54. /// <summary>The address of the actual chat server assigned from NameServer. Public for read only.</summary>
  55. public string FrontendAddress { get; private set; }
  56. /// <summary>Region used to connect to. Currently all chat is done in EU. It can make sense to use only one region for the whole game.</summary>
  57. private string chatRegion = "EU";
  58. /// <summary>Settable only before you connect! Defaults to "EU".</summary>
  59. public string ChatRegion
  60. {
  61. get { return this.chatRegion; }
  62. set { this.chatRegion = value; }
  63. }
  64. /// <summary>Current state of the ChatClient. Also use CanChat.</summary>
  65. public ChatState State { get; private set; }
  66. /// <summary> Disconnection cause. Check this inside <see cref="IChatClientListener.OnDisconnected"/>. </summary>
  67. public ChatDisconnectCause DisconnectedCause { get; private set; }
  68. /// <summary>
  69. /// Checks if this client is ready to send messages.
  70. /// </summary>
  71. public bool CanChat
  72. {
  73. get { return this.State == ChatState.ConnectedToFrontEnd && this.HasPeer; }
  74. }
  75. /// <summary>
  76. /// Checks if this client is ready to publish messages inside a public channel.
  77. /// </summary>
  78. /// <param name="channelName">The channel to do the check with.</param>
  79. /// <returns>Whether or not this client is ready to publish messages inside the public channel with the specified channelName.</returns>
  80. public bool CanChatInChannel(string channelName)
  81. {
  82. return this.CanChat && this.PublicChannels.ContainsKey(channelName) && !this.PublicChannelsUnsubscribing.Contains(channelName);
  83. }
  84. private bool HasPeer
  85. {
  86. get { return this.chatPeer != null; }
  87. }
  88. /// <summary>The version of your client. A new version also creates a new "virtual app" to separate players from older client versions.</summary>
  89. public string AppVersion { get; private set; }
  90. /// <summary>The AppID as assigned from the Photon Cloud.</summary>
  91. public string AppId { get; private set; }
  92. /// <summary>Settable only before you connect!</summary>
  93. public AuthenticationValues AuthValues { get; set; }
  94. /// <summary>The unique ID of a user/person, stored in AuthValues.UserId. Set it before you connect.</summary>
  95. /// <remarks>
  96. /// This value wraps AuthValues.UserId.
  97. /// It's not a nickname and we assume users with the same userID are the same person.</remarks>
  98. public string UserId
  99. {
  100. get
  101. {
  102. return (this.AuthValues != null) ? this.AuthValues.UserId : null;
  103. }
  104. private set
  105. {
  106. if (this.AuthValues == null)
  107. {
  108. this.AuthValues = new AuthenticationValues();
  109. }
  110. this.AuthValues.UserId = value;
  111. }
  112. }
  113. /// <summary>If greater than 0, new channels will limit the number of messages they cache locally.</summary>
  114. /// <remarks>
  115. /// This can be useful to limit the amount of memory used by chats.
  116. /// You can set a MessageLimit per channel but this value gets applied to new ones.
  117. ///
  118. /// Note:
  119. /// Changing this value, does not affect ChatChannels that are already in use!
  120. /// </remarks>
  121. public int MessageLimit;
  122. /// <summary>Limits the number of messages from private channel histories.</summary>
  123. /// <remarks>
  124. /// This is applied to all private channels on reconnect, as there is no explicit re-joining private channels.<br/>
  125. /// Default is -1, which gets available messages up to a maximum set by the server.<br/>
  126. /// A value of 0 gets you zero messages.<br/>
  127. /// The server's limit of messages may be lower. If so, the server's value will overrule this.<br/>
  128. /// </remarks>
  129. public int PrivateChatHistoryLength = -1;
  130. /// <summary> Public channels this client is subscribed to. </summary>
  131. public readonly Dictionary<string, ChatChannel> PublicChannels;
  132. /// <summary> Private channels in which this client has exchanged messages. </summary>
  133. public readonly Dictionary<string, ChatChannel> PrivateChannels;
  134. // channels being in unsubscribing process
  135. // items will be removed on successful unsubscription or subscription (the latter required after attempt to unsubscribe from not existing channel)
  136. private readonly HashSet<string> PublicChannelsUnsubscribing;
  137. private readonly IChatClientListener listener = null;
  138. /// <summary> The Chat Peer used by this client. </summary>
  139. public ChatPeer chatPeer = null;
  140. private const string ChatAppName = "chat";
  141. private bool didAuthenticate;
  142. private int? statusToSetWhenConnected;
  143. private object messageToSetWhenConnected;
  144. private int msDeltaForServiceCalls = 50;
  145. private int msTimestampOfLastServiceCall;
  146. /// <summary>Defines if a background thread will call SendOutgoingCommands, while your code calls Service to dispatch received messages.</summary>
  147. /// <remarks>
  148. /// The benefit of using a background thread to call SendOutgoingCommands is this:
  149. ///
  150. /// Even if your game logic is being paused, the background thread will keep the connection to the server up.
  151. /// On a lower level, acknowledgements and pings will prevent a server-side timeout while (e.g.) Unity loads assets.
  152. ///
  153. /// Your game logic still has to call Service regularly, or else incoming messages are not dispatched.
  154. /// As this typically triggers UI updates, it's easier to call Service from the main/UI thread.
  155. /// </remarks>
  156. public bool UseBackgroundWorkerForSending { get; set; }
  157. /// <summary>Exposes the TransportProtocol of the used PhotonPeer. Settable while not connected.</summary>
  158. public ConnectionProtocol TransportProtocol
  159. {
  160. get { return this.chatPeer.TransportProtocol; }
  161. set
  162. {
  163. if (this.chatPeer == null || this.chatPeer.PeerState != PeerStateValue.Disconnected)
  164. {
  165. this.listener.DebugReturn(DebugLevel.WARNING, "Can't set TransportProtocol. Disconnect first! " + ((this.chatPeer != null) ? "PeerState: " + this.chatPeer.PeerState : "The chatPeer is null."));
  166. return;
  167. }
  168. this.chatPeer.TransportProtocol = value;
  169. }
  170. }
  171. /// <summary>Defines which IPhotonSocket class to use per ConnectionProtocol.</summary>
  172. /// <remarks>
  173. /// Several platforms have special Socket implementations and slightly different APIs.
  174. /// To accomodate this, switching the socket implementation for a network protocol was made available.
  175. /// By default, UDP and TCP have socket implementations assigned.
  176. ///
  177. /// You only need to set the SocketImplementationConfig once, after creating a PhotonPeer
  178. /// and before connecting. If you switch the TransportProtocol, the correct implementation is being used.
  179. /// </remarks>
  180. public Dictionary<ConnectionProtocol, Type> SocketImplementationConfig
  181. {
  182. get { return this.chatPeer.SocketImplementationConfig; }
  183. }
  184. /// <summary>
  185. /// Chat client constructor.
  186. /// </summary>
  187. /// <param name="listener">The chat listener implementation.</param>
  188. /// <param name="protocol">Connection protocol to be used by this client. Default is <see cref="ConnectionProtocol.Udp"/>.</param>
  189. public ChatClient(IChatClientListener listener, ConnectionProtocol protocol = ConnectionProtocol.Udp)
  190. {
  191. this.listener = listener;
  192. this.State = ChatState.Uninitialized;
  193. this.chatPeer = new ChatPeer(this, protocol);
  194. this.chatPeer.SerializationProtocolType = SerializationProtocol.GpBinaryV18;
  195. this.PublicChannels = new Dictionary<string, ChatChannel>();
  196. this.PrivateChannels = new Dictionary<string, ChatChannel>();
  197. this.PublicChannelsUnsubscribing = new HashSet<string>();
  198. }
  199. public bool ConnectUsingSettings(ChatAppSettings appSettings)
  200. {
  201. if (appSettings == null)
  202. {
  203. this.listener.DebugReturn(DebugLevel.ERROR, "ConnectUsingSettings failed. The appSettings can't be null.'");
  204. return false;
  205. }
  206. if (!string.IsNullOrEmpty(appSettings.FixedRegion))
  207. {
  208. this.ChatRegion = appSettings.FixedRegion;
  209. }
  210. this.DebugOut = appSettings.NetworkLogging;
  211. this.TransportProtocol = appSettings.Protocol;
  212. this.EnableProtocolFallback = appSettings.EnableProtocolFallback;
  213. if (!appSettings.IsDefaultNameServer)
  214. {
  215. this.chatPeer.NameServerHost = appSettings.Server;
  216. this.chatPeer.NameServerPortOverride = appSettings.Port;
  217. }
  218. return this.Connect(appSettings.AppIdChat, appSettings.AppVersion, this.AuthValues);
  219. }
  220. /// <summary>
  221. /// Connects this client to the Photon Chat Cloud service, which will also authenticate the user (and set a UserId).
  222. /// </summary>
  223. /// <param name="appId">Get your Photon Chat AppId from the <a href="https://dashboard.photonengine.com">Dashboard</a>.</param>
  224. /// <param name="appVersion">Any version string you make up. Used to separate users and variants of your clients, which might be incompatible.</param>
  225. /// <param name="authValues">Values for authentication. You can leave this null, if you set a UserId before. If you set authValues, they will override any UserId set before.</param>
  226. /// <returns></returns>
  227. public bool Connect(string appId, string appVersion, AuthenticationValues authValues)
  228. {
  229. this.chatPeer.TimePingInterval = 3000;
  230. this.DisconnectedCause = ChatDisconnectCause.None;
  231. if (authValues != null)
  232. {
  233. this.AuthValues = authValues;
  234. }
  235. this.AppId = appId;
  236. this.AppVersion = appVersion;
  237. this.didAuthenticate = false;
  238. this.chatPeer.QuickResendAttempts = 2;
  239. this.chatPeer.SentCountAllowance = 7;
  240. // clean all channels
  241. this.PublicChannels.Clear();
  242. this.PrivateChannels.Clear();
  243. this.PublicChannelsUnsubscribing.Clear();
  244. #if UNITY_WEBGL
  245. if (this.TransportProtocol == ConnectionProtocol.Tcp || this.TransportProtocol == ConnectionProtocol.Udp)
  246. {
  247. this.listener.DebugReturn(DebugLevel.WARNING, "WebGL requires WebSockets. Switching TransportProtocol to WebSocketSecure.");
  248. this.TransportProtocol = ConnectionProtocol.WebSocketSecure;
  249. }
  250. #endif
  251. this.NameServerAddress = this.chatPeer.NameServerAddress;
  252. bool isConnecting = this.chatPeer.Connect();
  253. if (isConnecting)
  254. {
  255. this.State = ChatState.ConnectingToNameServer;
  256. }
  257. if (this.UseBackgroundWorkerForSending)
  258. {
  259. #if UNITY_SWITCH
  260. SupportClass.StartBackgroundCalls(this.SendOutgoingInBackground, this.msDeltaForServiceCalls); // as workaround, we don't name the Thread.
  261. #else
  262. SupportClass.StartBackgroundCalls(this.SendOutgoingInBackground, this.msDeltaForServiceCalls, "ChatClient Service Thread");
  263. #endif
  264. }
  265. return isConnecting;
  266. }
  267. /// <summary>
  268. /// Connects this client to the Photon Chat Cloud service, which will also authenticate the user (and set a UserId).
  269. /// This also sets an online status once connected. By default it will set user status to <see cref="ChatUserStatus.Online"/>.
  270. /// See <see cref="SetOnlineStatus(int,object)"/> for more information.
  271. /// </summary>
  272. /// <param name="appId">Get your Photon Chat AppId from the <a href="https://dashboard.photonengine.com">Dashboard</a>.</param>
  273. /// <param name="appVersion">Any version string you make up. Used to separate users and variants of your clients, which might be incompatible.</param>
  274. /// <param name="authValues">Values for authentication. You can leave this null, if you set a UserId before. If you set authValues, they will override any UserId set before.</param>
  275. /// <param name="status">User status to set when connected. Predefined states are in class <see cref="ChatUserStatus"/>. Other values can be used at will.</param>
  276. /// <param name="message">Optional status Also sets a status-message which your friends can get.</param>
  277. /// <returns>If the connection attempt could be sent at all.</returns>
  278. public bool ConnectAndSetStatus(string appId, string appVersion, AuthenticationValues authValues,
  279. int status = ChatUserStatus.Online, object message = null)
  280. {
  281. statusToSetWhenConnected = status;
  282. messageToSetWhenConnected = message;
  283. return Connect(appId, appVersion, authValues);
  284. }
  285. /// <summary>
  286. /// Must be called regularly to keep connection between client and server alive and to process incoming messages.
  287. /// </summary>
  288. /// <remarks>
  289. /// This method limits the effort it does automatically using the private variable msDeltaForServiceCalls.
  290. /// That value is lower for connect and multiplied by 4 when chat-server connection is ready.
  291. /// </remarks>
  292. public void Service()
  293. {
  294. // Dispatch until every already-received message got dispatched
  295. while (this.HasPeer && this.chatPeer.DispatchIncomingCommands())
  296. {
  297. }
  298. // if there is no background thread for sending, Service() will do that as well, in intervals
  299. if (!this.UseBackgroundWorkerForSending)
  300. {
  301. if (Environment.TickCount - this.msTimestampOfLastServiceCall > this.msDeltaForServiceCalls || this.msTimestampOfLastServiceCall == 0)
  302. {
  303. this.msTimestampOfLastServiceCall = Environment.TickCount;
  304. while (this.HasPeer && this.chatPeer.SendOutgoingCommands())
  305. {
  306. }
  307. }
  308. }
  309. }
  310. /// <summary>
  311. /// Called by a separate thread, this sends outgoing commands of this peer, as long as it's connected.
  312. /// </summary>
  313. /// <returns>True as long as the client is not disconnected.</returns>
  314. private bool SendOutgoingInBackground()
  315. {
  316. while (this.HasPeer && this.chatPeer.SendOutgoingCommands())
  317. {
  318. }
  319. return this.State != ChatState.Disconnected;
  320. }
  321. /// <summary> Obsolete: Better use UseBackgroundWorkerForSending and Service(). </summary>
  322. [Obsolete("Better use UseBackgroundWorkerForSending and Service().")]
  323. public void SendAcksOnly()
  324. {
  325. if (this.HasPeer) this.chatPeer.SendAcksOnly();
  326. }
  327. /// <summary>
  328. /// Disconnects from the Chat Server by sending a "disconnect command", which prevents a timeout server-side.
  329. /// </summary>
  330. public void Disconnect(ChatDisconnectCause cause = ChatDisconnectCause.DisconnectByClientLogic)
  331. {
  332. if (this.HasPeer && this.chatPeer.PeerState != PeerStateValue.Disconnected)
  333. {
  334. this.State = ChatState.Disconnecting;
  335. this.DisconnectedCause = cause;
  336. this.chatPeer.Disconnect();
  337. }
  338. }
  339. /// <summary>
  340. /// Locally shuts down the connection to the Chat Server. This resets states locally but the server will have to timeout this peer.
  341. /// </summary>
  342. public void StopThread()
  343. {
  344. if (this.HasPeer)
  345. {
  346. this.chatPeer.StopThread();
  347. }
  348. }
  349. /// <summary>Sends operation to subscribe to a list of channels by name.</summary>
  350. /// <param name="channels">List of channels to subscribe to. Avoid null or empty values.</param>
  351. /// <returns>If the operation could be sent at all (Example: Fails if not connected to Chat Server).</returns>
  352. public bool Subscribe(string[] channels)
  353. {
  354. return this.Subscribe(channels, 0);
  355. }
  356. /// <summary>
  357. /// Sends operation to subscribe to a list of channels by name and possibly retrieve messages we did not receive while unsubscribed.
  358. /// </summary>
  359. /// <param name="channels">List of channels to subscribe to. Avoid null or empty values.</param>
  360. /// <param name="lastMsgIds">ID of last message received per channel. Useful when re subscribing to receive only messages we missed.</param>
  361. /// <returns>If the operation could be sent at all (Example: Fails if not connected to Chat Server).</returns>
  362. public bool Subscribe(string[] channels, int[] lastMsgIds)
  363. {
  364. if (!this.CanChat)
  365. {
  366. if (this.DebugOut >= DebugLevel.ERROR)
  367. {
  368. this.listener.DebugReturn(DebugLevel.ERROR, "Subscribe called while not connected to front end server.");
  369. }
  370. return false;
  371. }
  372. if (channels == null || channels.Length == 0)
  373. {
  374. if (this.DebugOut >= DebugLevel.WARNING)
  375. {
  376. this.listener.DebugReturn(DebugLevel.WARNING, "Subscribe can't be called for empty or null channels-list.");
  377. }
  378. return false;
  379. }
  380. for (int i = 0; i < channels.Length; i++)
  381. {
  382. if (string.IsNullOrEmpty(channels[i]))
  383. {
  384. if (this.DebugOut >= DebugLevel.ERROR)
  385. {
  386. this.listener.DebugReturn(DebugLevel.ERROR, string.Format("Subscribe can't be called with a null or empty channel name at index {0}.", i));
  387. }
  388. return false;
  389. }
  390. }
  391. if (lastMsgIds == null || lastMsgIds.Length != channels.Length)
  392. {
  393. if (this.DebugOut >= DebugLevel.ERROR)
  394. {
  395. this.listener.DebugReturn(DebugLevel.ERROR, "Subscribe can't be called when \"lastMsgIds\" array is null or does not have the same length as \"channels\" array.");
  396. }
  397. return false;
  398. }
  399. Dictionary<byte, object> opParameters = new Dictionary<byte, object>
  400. {
  401. { ChatParameterCode.Channels, channels },
  402. { ChatParameterCode.MsgIds, lastMsgIds},
  403. { ChatParameterCode.HistoryLength, -1 } // server will decide how many messages to send to client
  404. };
  405. return this.chatPeer.SendOperation(ChatOperationCode.Subscribe, opParameters, SendOptions.SendReliable);
  406. }
  407. /// <summary>
  408. /// Sends operation to subscribe client to channels, optionally fetching a number of messages from the cache.
  409. /// </summary>
  410. /// <remarks>
  411. /// Subscribes channels will forward new messages to this user. Use PublishMessage to do so.
  412. /// The messages cache is limited but can be useful to get into ongoing conversations, if that's needed.
  413. /// </remarks>
  414. /// <param name="channels">List of channels to subscribe to. Avoid null or empty values.</param>
  415. /// <param name="messagesFromHistory">0: no history. 1 and higher: number of messages in history. -1: all available history.</param>
  416. /// <returns>If the operation could be sent at all (Example: Fails if not connected to Chat Server).</returns>
  417. public bool Subscribe(string[] channels, int messagesFromHistory)
  418. {
  419. if (!this.CanChat)
  420. {
  421. if (this.DebugOut >= DebugLevel.ERROR)
  422. {
  423. this.listener.DebugReturn(DebugLevel.ERROR, "Subscribe called while not connected to front end server.");
  424. }
  425. return false;
  426. }
  427. if (channels == null || channels.Length == 0)
  428. {
  429. if (this.DebugOut >= DebugLevel.WARNING)
  430. {
  431. this.listener.DebugReturn(DebugLevel.WARNING, "Subscribe can't be called for empty or null channels-list.");
  432. }
  433. return false;
  434. }
  435. return this.SendChannelOperation(channels, (byte)ChatOperationCode.Subscribe, messagesFromHistory);
  436. }
  437. /// <summary>Unsubscribes from a list of channels, which stops getting messages from those.</summary>
  438. /// <remarks>
  439. /// The client will remove these channels from the PublicChannels dictionary once the server sent a response to this request.
  440. ///
  441. /// The request will be sent to the server and IChatClientListener.OnUnsubscribed gets called when the server
  442. /// actually removed the channel subscriptions.
  443. ///
  444. /// Unsubscribe will fail if you include null or empty channel names.
  445. /// </remarks>
  446. /// <param name="channels">Names of channels to unsubscribe.</param>
  447. /// <returns>False, if not connected to a chat server.</returns>
  448. public bool Unsubscribe(string[] channels)
  449. {
  450. if (!this.CanChat)
  451. {
  452. if (this.DebugOut >= DebugLevel.ERROR)
  453. {
  454. this.listener.DebugReturn(DebugLevel.ERROR, "Unsubscribe called while not connected to front end server.");
  455. }
  456. return false;
  457. }
  458. if (channels == null || channels.Length == 0)
  459. {
  460. if (this.DebugOut >= DebugLevel.WARNING)
  461. {
  462. this.listener.DebugReturn(DebugLevel.WARNING, "Unsubscribe can't be called for empty or null channels-list.");
  463. }
  464. return false;
  465. }
  466. foreach (string ch in channels)
  467. {
  468. this.PublicChannelsUnsubscribing.Add(ch);
  469. }
  470. return this.SendChannelOperation(channels, ChatOperationCode.Unsubscribe, 0);
  471. }
  472. /// <summary>Sends a message to a public channel which this client subscribed to.</summary>
  473. /// <remarks>
  474. /// Before you publish to a channel, you have to subscribe it.
  475. /// Everyone in that channel will get the message.
  476. /// </remarks>
  477. /// <param name="channelName">Name of the channel to publish to.</param>
  478. /// <param name="message">Your message (string or any serializable data).</param>
  479. /// <param name="forwardAsWebhook">Optionally, public messages can be forwarded as webhooks. Configure webhooks for your Chat app to use this.</param>
  480. /// <returns>False if the client is not yet ready to send messages.</returns>
  481. public bool PublishMessage(string channelName, object message, bool forwardAsWebhook = false)
  482. {
  483. return this.publishMessage(channelName, message, true, forwardAsWebhook);
  484. }
  485. internal bool PublishMessageUnreliable(string channelName, object message, bool forwardAsWebhook = false)
  486. {
  487. return this.publishMessage(channelName, message, false, forwardAsWebhook);
  488. }
  489. private bool publishMessage(string channelName, object message, bool reliable, bool forwardAsWebhook = false)
  490. {
  491. if (!this.CanChat)
  492. {
  493. if (this.DebugOut >= DebugLevel.ERROR)
  494. {
  495. this.listener.DebugReturn(DebugLevel.ERROR, "PublishMessage called while not connected to front end server.");
  496. }
  497. return false;
  498. }
  499. if (string.IsNullOrEmpty(channelName) || message == null)
  500. {
  501. if (this.DebugOut >= DebugLevel.WARNING)
  502. {
  503. this.listener.DebugReturn(DebugLevel.WARNING, "PublishMessage parameters must be non-null and not empty.");
  504. }
  505. return false;
  506. }
  507. Dictionary<byte, object> parameters = new Dictionary<byte, object>
  508. {
  509. { (byte)ChatParameterCode.Channel, channelName },
  510. { (byte)ChatParameterCode.Message, message }
  511. };
  512. if (forwardAsWebhook)
  513. {
  514. parameters.Add(ChatParameterCode.WebFlags, (byte)0x1);
  515. }
  516. return this.chatPeer.SendOperation(ChatOperationCode.Publish, parameters, new SendOptions() { Reliability = reliable });
  517. }
  518. /// <summary>
  519. /// Sends a private message to a single target user. Calls OnPrivateMessage on the receiving client.
  520. /// </summary>
  521. /// <param name="target">Username to send this message to.</param>
  522. /// <param name="message">The message you want to send. Can be a simple string or anything serializable.</param>
  523. /// <param name="forwardAsWebhook">Optionally, private messages can be forwarded as webhooks. Configure webhooks for your Chat app to use this.</param>
  524. /// <returns>True if this clients can send the message to the server.</returns>
  525. public bool SendPrivateMessage(string target, object message, bool forwardAsWebhook = false)
  526. {
  527. return this.SendPrivateMessage(target, message, false, forwardAsWebhook);
  528. }
  529. /// <summary>
  530. /// Sends a private message to a single target user. Calls OnPrivateMessage on the receiving client.
  531. /// </summary>
  532. /// <param name="target">Username to send this message to.</param>
  533. /// <param name="message">The message you want to send. Can be a simple string or anything serializable.</param>
  534. /// <param name="encrypt">Optionally, private messages can be encrypted. Encryption is not end-to-end as the server decrypts the message.</param>
  535. /// <param name="forwardAsWebhook">Optionally, private messages can be forwarded as webhooks. Configure webhooks for your Chat app to use this.</param>
  536. /// <returns>True if this clients can send the message to the server.</returns>
  537. public bool SendPrivateMessage(string target, object message, bool encrypt, bool forwardAsWebhook)
  538. {
  539. return this.sendPrivateMessage(target, message, encrypt, true, forwardAsWebhook);
  540. }
  541. internal bool SendPrivateMessageUnreliable(string target, object message, bool encrypt, bool forwardAsWebhook = false)
  542. {
  543. return this.sendPrivateMessage(target, message, encrypt, false, forwardAsWebhook);
  544. }
  545. private bool sendPrivateMessage(string target, object message, bool encrypt, bool reliable, bool forwardAsWebhook = false)
  546. {
  547. if (!this.CanChat)
  548. {
  549. if (this.DebugOut >= DebugLevel.ERROR)
  550. {
  551. this.listener.DebugReturn(DebugLevel.ERROR, "SendPrivateMessage called while not connected to front end server.");
  552. }
  553. return false;
  554. }
  555. if (string.IsNullOrEmpty(target) || message == null)
  556. {
  557. if (this.DebugOut >= DebugLevel.WARNING)
  558. {
  559. this.listener.DebugReturn(DebugLevel.WARNING, "SendPrivateMessage parameters must be non-null and not empty.");
  560. }
  561. return false;
  562. }
  563. Dictionary<byte, object> parameters = new Dictionary<byte, object>
  564. {
  565. { ChatParameterCode.UserId, target },
  566. { ChatParameterCode.Message, message }
  567. };
  568. if (forwardAsWebhook)
  569. {
  570. parameters.Add(ChatParameterCode.WebFlags, (byte)0x1);
  571. }
  572. return this.chatPeer.SendOperation(ChatOperationCode.SendPrivate, parameters, new SendOptions() { Reliability = reliable, Encrypt = encrypt });
  573. }
  574. /// <summary>Sets the user's status (pre-defined or custom) and an optional message.</summary>
  575. /// <remarks>
  576. /// The predefined status values can be found in class ChatUserStatus.
  577. /// State ChatUserStatus.Invisible will make you offline for everyone and send no message.
  578. ///
  579. /// You can set custom values in the status integer. Aside from the pre-configured ones,
  580. /// all states will be considered visible and online. Else, no one would see the custom state.
  581. ///
  582. /// The message object can be anything that Photon can serialize, including (but not limited to)
  583. /// Hashtable, object[] and string. This value is defined by your own conventions.
  584. /// </remarks>
  585. /// <param name="status">Predefined states are in class ChatUserStatus. Other values can be used at will.</param>
  586. /// <param name="message">Optional string message or null.</param>
  587. /// <param name="skipMessage">If true, the message gets ignored. It can be null but won't replace any current message.</param>
  588. /// <returns>True if the operation gets called on the server.</returns>
  589. private bool SetOnlineStatus(int status, object message, bool skipMessage)
  590. {
  591. if (!this.CanChat)
  592. {
  593. if (this.DebugOut >= DebugLevel.ERROR)
  594. {
  595. this.listener.DebugReturn(DebugLevel.ERROR, "SetOnlineStatus called while not connected to front end server.");
  596. }
  597. return false;
  598. }
  599. Dictionary<byte, object> parameters = new Dictionary<byte, object>
  600. {
  601. { ChatParameterCode.Status, status },
  602. };
  603. if (skipMessage)
  604. {
  605. parameters[ChatParameterCode.SkipMessage] = true;
  606. }
  607. else
  608. {
  609. parameters[ChatParameterCode.Message] = message;
  610. }
  611. return this.chatPeer.SendOperation(ChatOperationCode.UpdateStatus, parameters, SendOptions.SendReliable);
  612. }
  613. /// <summary>Sets the user's status without changing your status-message.</summary>
  614. /// <remarks>
  615. /// The predefined status values can be found in class ChatUserStatus.
  616. /// State ChatUserStatus.Invisible will make you offline for everyone and send no message.
  617. ///
  618. /// You can set custom values in the status integer. Aside from the pre-configured ones,
  619. /// all states will be considered visible and online. Else, no one would see the custom state.
  620. ///
  621. /// This overload does not change the set message.
  622. /// </remarks>
  623. /// <param name="status">Predefined states are in class ChatUserStatus. Other values can be used at will.</param>
  624. /// <returns>True if the operation gets called on the server.</returns>
  625. public bool SetOnlineStatus(int status)
  626. {
  627. return this.SetOnlineStatus(status, null, true);
  628. }
  629. /// <summary>Sets the user's status without changing your status-message.</summary>
  630. /// <remarks>
  631. /// The predefined status values can be found in class ChatUserStatus.
  632. /// State ChatUserStatus.Invisible will make you offline for everyone and send no message.
  633. ///
  634. /// You can set custom values in the status integer. Aside from the pre-configured ones,
  635. /// all states will be considered visible and online. Else, no one would see the custom state.
  636. ///
  637. /// The message object can be anything that Photon can serialize, including (but not limited to)
  638. /// Hashtable, object[] and string. This value is defined by your own conventions.
  639. /// </remarks>
  640. /// <param name="status">Predefined states are in class ChatUserStatus. Other values can be used at will.</param>
  641. /// <param name="message">Also sets a status-message which your friends can get.</param>
  642. /// <returns>True if the operation gets called on the server.</returns>
  643. public bool SetOnlineStatus(int status, object message)
  644. {
  645. return this.SetOnlineStatus(status, message, false);
  646. }
  647. /// <summary>
  648. /// Adds friends to a list on the Chat Server which will send you status updates for those.
  649. /// </summary>
  650. /// <remarks>
  651. /// AddFriends and RemoveFriends enable clients to handle their friend list
  652. /// in the Photon Chat server. Having users on your friends list gives you access
  653. /// to their current online status (and whatever info your client sets in it).
  654. ///
  655. /// Each user can set an online status consisting of an integer and an arbitrary
  656. /// (serializable) object. The object can be null, Hashtable, object[] or anything
  657. /// else Photon can serialize.
  658. ///
  659. /// The status is published automatically to friends (anyone who set your user ID
  660. /// with AddFriends).
  661. ///
  662. /// Photon flushes friends-list when a chat client disconnects, so it has to be
  663. /// set each time. If your community API gives you access to online status already,
  664. /// you could filter and set online friends in AddFriends.
  665. ///
  666. /// Actual friend relations are not persistent and have to be stored outside
  667. /// of Photon.
  668. /// </remarks>
  669. /// <param name="friends">Array of friend userIds.</param>
  670. /// <returns>If the operation could be sent.</returns>
  671. public bool AddFriends(string[] friends)
  672. {
  673. if (!this.CanChat)
  674. {
  675. if (this.DebugOut >= DebugLevel.ERROR)
  676. {
  677. this.listener.DebugReturn(DebugLevel.ERROR, "AddFriends called while not connected to front end server.");
  678. }
  679. return false;
  680. }
  681. if (friends == null || friends.Length == 0)
  682. {
  683. if (this.DebugOut >= DebugLevel.WARNING)
  684. {
  685. this.listener.DebugReturn(DebugLevel.WARNING, "AddFriends can't be called for empty or null list.");
  686. }
  687. return false;
  688. }
  689. if (friends.Length > FriendRequestListMax)
  690. {
  691. if (this.DebugOut >= DebugLevel.WARNING)
  692. {
  693. this.listener.DebugReturn(DebugLevel.WARNING, "AddFriends max list size exceeded: " + friends.Length + " > " + FriendRequestListMax);
  694. }
  695. return false;
  696. }
  697. Dictionary<byte, object> parameters = new Dictionary<byte, object>
  698. {
  699. { ChatParameterCode.Friends, friends },
  700. };
  701. return this.chatPeer.SendOperation(ChatOperationCode.AddFriends, parameters, SendOptions.SendReliable);
  702. }
  703. /// <summary>
  704. /// Removes the provided entries from the list on the Chat Server and stops their status updates.
  705. /// </summary>
  706. /// <remarks>
  707. /// Photon flushes friends-list when a chat client disconnects. Unless you want to
  708. /// remove individual entries, you don't have to RemoveFriends.
  709. ///
  710. /// AddFriends and RemoveFriends enable clients to handle their friend list
  711. /// in the Photon Chat server. Having users on your friends list gives you access
  712. /// to their current online status (and whatever info your client sets in it).
  713. ///
  714. /// Each user can set an online status consisting of an integer and an arbitratry
  715. /// (serializable) object. The object can be null, Hashtable, object[] or anything
  716. /// else Photon can serialize.
  717. ///
  718. /// The status is published automatically to friends (anyone who set your user ID
  719. /// with AddFriends).
  720. ///
  721. /// Photon flushes friends-list when a chat client disconnects, so it has to be
  722. /// set each time. If your community API gives you access to online status already,
  723. /// you could filter and set online friends in AddFriends.
  724. ///
  725. /// Actual friend relations are not persistent and have to be stored outside
  726. /// of Photon.
  727. ///
  728. /// AddFriends and RemoveFriends enable clients to handle their friend list
  729. /// in the Photon Chat server. Having users on your friends list gives you access
  730. /// to their current online status (and whatever info your client sets in it).
  731. ///
  732. /// Each user can set an online status consisting of an integer and an arbitratry
  733. /// (serializable) object. The object can be null, Hashtable, object[] or anything
  734. /// else Photon can serialize.
  735. ///
  736. /// The status is published automatically to friends (anyone who set your user ID
  737. /// with AddFriends).
  738. ///
  739. ///
  740. /// Actual friend relations are not persistent and have to be stored outside
  741. /// of Photon.
  742. /// </remarks>
  743. /// <param name="friends">Array of friend userIds.</param>
  744. /// <returns>If the operation could be sent.</returns>
  745. public bool RemoveFriends(string[] friends)
  746. {
  747. if (!this.CanChat)
  748. {
  749. if (this.DebugOut >= DebugLevel.ERROR)
  750. {
  751. this.listener.DebugReturn(DebugLevel.ERROR, "RemoveFriends called while not connected to front end server.");
  752. }
  753. return false;
  754. }
  755. if (friends == null || friends.Length == 0)
  756. {
  757. if (this.DebugOut >= DebugLevel.WARNING)
  758. {
  759. this.listener.DebugReturn(DebugLevel.WARNING, "RemoveFriends can't be called for empty or null list.");
  760. }
  761. return false;
  762. }
  763. if (friends.Length > FriendRequestListMax)
  764. {
  765. if (this.DebugOut >= DebugLevel.WARNING)
  766. {
  767. this.listener.DebugReturn(DebugLevel.WARNING, "RemoveFriends max list size exceeded: " + friends.Length + " > " + FriendRequestListMax);
  768. }
  769. return false;
  770. }
  771. Dictionary<byte, object> parameters = new Dictionary<byte, object>
  772. {
  773. { ChatParameterCode.Friends, friends },
  774. };
  775. return this.chatPeer.SendOperation(ChatOperationCode.RemoveFriends, parameters, SendOptions.SendReliable);
  776. }
  777. /// <summary>
  778. /// Get you the (locally used) channel name for the chat between this client and another user.
  779. /// </summary>
  780. /// <param name="userName">Remote user's name or UserId.</param>
  781. /// <returns>The (locally used) channel name for a private channel.</returns>
  782. /// <remarks>Do not subscribe to this channel.
  783. /// Private channels do not need to be explicitly subscribed to.
  784. /// Use this for debugging purposes mainly.</remarks>
  785. public string GetPrivateChannelNameByUser(string userName)
  786. {
  787. return string.Format("{0}:{1}", this.UserId, userName);
  788. }
  789. /// <summary>
  790. /// Simplified access to either private or public channels by name.
  791. /// </summary>
  792. /// <param name="channelName">Name of the channel to get. For private channels, the channel-name is composed of both user's names.</param>
  793. /// <param name="isPrivate">Define if you expect a private or public channel.</param>
  794. /// <param name="channel">Out parameter gives you the found channel, if any.</param>
  795. /// <returns>True if the channel was found.</returns>
  796. /// <remarks>Public channels exist only when subscribed to them.
  797. /// Private channels exist only when at least one message is exchanged with the target user privately.</remarks>
  798. public bool TryGetChannel(string channelName, bool isPrivate, out ChatChannel channel)
  799. {
  800. if (!isPrivate)
  801. {
  802. return this.PublicChannels.TryGetValue(channelName, out channel);
  803. }
  804. else
  805. {
  806. return this.PrivateChannels.TryGetValue(channelName, out channel);
  807. }
  808. }
  809. /// <summary>
  810. /// Simplified access to all channels by name. Checks public channels first, then private ones.
  811. /// </summary>
  812. /// <param name="channelName">Name of the channel to get.</param>
  813. /// <param name="channel">Out parameter gives you the found channel, if any.</param>
  814. /// <returns>True if the channel was found.</returns>
  815. /// <remarks>Public channels exist only when subscribed to them.
  816. /// Private channels exist only when at least one message is exchanged with the target user privately.</remarks>
  817. public bool TryGetChannel(string channelName, out ChatChannel channel)
  818. {
  819. bool found = false;
  820. found = this.PublicChannels.TryGetValue(channelName, out channel);
  821. if (found) return true;
  822. found = this.PrivateChannels.TryGetValue(channelName, out channel);
  823. return found;
  824. }
  825. /// <summary>
  826. /// Simplified access to private channels by target user.
  827. /// </summary>
  828. /// <param name="userId">UserId of the target user in the private channel.</param>
  829. /// <param name="channel">Out parameter gives you the found channel, if any.</param>
  830. /// <returns>True if the channel was found.</returns>
  831. public bool TryGetPrivateChannelByUser(string userId, out ChatChannel channel)
  832. {
  833. channel = null;
  834. if (string.IsNullOrEmpty(userId))
  835. {
  836. return false;
  837. }
  838. string channelName = this.GetPrivateChannelNameByUser(userId);
  839. return this.TryGetChannel(channelName, true, out channel);
  840. }
  841. /// <summary>
  842. /// Sets the level (and amount) of debug output provided by the library.
  843. /// </summary>
  844. /// <remarks>
  845. /// This affects the callbacks to IChatClientListener.DebugReturn.
  846. /// Default Level: Error.
  847. /// </remarks>
  848. public DebugLevel DebugOut
  849. {
  850. set { this.chatPeer.DebugOut = value; }
  851. get { return this.chatPeer.DebugOut; }
  852. }
  853. #region Private methods area
  854. #region IPhotonPeerListener implementation
  855. void IPhotonPeerListener.DebugReturn(DebugLevel level, string message)
  856. {
  857. this.listener.DebugReturn(level, message);
  858. }
  859. void IPhotonPeerListener.OnEvent(EventData eventData)
  860. {
  861. switch (eventData.Code)
  862. {
  863. case ChatEventCode.ChatMessages:
  864. this.HandleChatMessagesEvent(eventData);
  865. break;
  866. case ChatEventCode.PrivateMessage:
  867. this.HandlePrivateMessageEvent(eventData);
  868. break;
  869. case ChatEventCode.StatusUpdate:
  870. this.HandleStatusUpdate(eventData);
  871. break;
  872. case ChatEventCode.Subscribe:
  873. this.HandleSubscribeEvent(eventData);
  874. break;
  875. case ChatEventCode.Unsubscribe:
  876. this.HandleUnsubscribeEvent(eventData);
  877. break;
  878. case ChatEventCode.UserSubscribed:
  879. this.HandleUserSubscribedEvent(eventData);
  880. break;
  881. case ChatEventCode.UserUnsubscribed:
  882. this.HandleUserUnsubscribedEvent(eventData);
  883. break;
  884. #if CHAT_EXTENDED
  885. case ChatEventCode.PropertiesChanged:
  886. this.HandlePropertiesChanged(eventData);
  887. break;
  888. case ChatEventCode.ErrorInfo:
  889. this.HandleErrorInfoEvent(eventData);
  890. break;
  891. #endif
  892. }
  893. }
  894. void IPhotonPeerListener.OnOperationResponse(OperationResponse operationResponse)
  895. {
  896. switch (operationResponse.OperationCode)
  897. {
  898. case (byte)ChatOperationCode.Authenticate:
  899. this.HandleAuthResponse(operationResponse);
  900. break;
  901. // the following operations usually don't return useful data and no error.
  902. case (byte)ChatOperationCode.Subscribe:
  903. case (byte)ChatOperationCode.Unsubscribe:
  904. case (byte)ChatOperationCode.Publish:
  905. case (byte)ChatOperationCode.SendPrivate:
  906. default:
  907. if ((operationResponse.ReturnCode != 0) && (this.DebugOut >= DebugLevel.ERROR))
  908. {
  909. if (operationResponse.ReturnCode == -2)
  910. {
  911. this.listener.DebugReturn(DebugLevel.ERROR, string.Format("Chat Operation {0} unknown on server. Check your AppId and make sure it's for a Chat application.", operationResponse.OperationCode));
  912. }
  913. else
  914. {
  915. this.listener.DebugReturn(DebugLevel.ERROR, string.Format("Chat Operation {0} failed (Code: {1}). Debug Message: {2}", operationResponse.OperationCode, operationResponse.ReturnCode, operationResponse.DebugMessage));
  916. }
  917. }
  918. break;
  919. }
  920. }
  921. void IPhotonPeerListener.OnStatusChanged(StatusCode statusCode)
  922. {
  923. switch (statusCode)
  924. {
  925. case StatusCode.Connect:
  926. if (!this.chatPeer.IsProtocolSecure)
  927. {
  928. if (!this.chatPeer.EstablishEncryption())
  929. {
  930. if (this.DebugOut >= DebugLevel.ERROR)
  931. {
  932. this.listener.DebugReturn(DebugLevel.ERROR, "Error establishing encryption");
  933. }
  934. }
  935. }
  936. else
  937. {
  938. this.TryAuthenticateOnNameServer();
  939. }
  940. if (this.State == ChatState.ConnectingToNameServer)
  941. {
  942. this.State = ChatState.ConnectedToNameServer;
  943. this.listener.OnChatStateChange(this.State);
  944. }
  945. else if (this.State == ChatState.ConnectingToFrontEnd)
  946. {
  947. if (!this.AuthenticateOnFrontEnd())
  948. {
  949. if (this.DebugOut >= DebugLevel.ERROR)
  950. {
  951. this.listener.DebugReturn(DebugLevel.ERROR, string.Format("Error authenticating on frontend! Check log output, AuthValues and if you're connected. State: {0}", this.State));
  952. }
  953. }
  954. }
  955. break;
  956. case StatusCode.EncryptionEstablished:
  957. // once encryption is available, the client should send one (secure) authenticate. it includes the AppId (which identifies your app on the Photon Cloud)
  958. this.TryAuthenticateOnNameServer();
  959. break;
  960. case StatusCode.Disconnect:
  961. switch (this.State)
  962. {
  963. case ChatState.ConnectWithFallbackProtocol:
  964. this.EnableProtocolFallback = false; // the client does a fallback only one time
  965. this.chatPeer.NameServerPortOverride = 0; // resets a value in the peer only (as we change the protocol, the port has to change, too)
  966. this.chatPeer.TransportProtocol = (this.chatPeer.TransportProtocol == ConnectionProtocol.Tcp) ? ConnectionProtocol.Udp : ConnectionProtocol.Tcp;
  967. this.Connect(this.AppId, this.AppVersion, null);
  968. // the client now has to return, instead of break, to avoid further processing of the disconnect call
  969. return;
  970. case ChatState.Authenticated:
  971. this.ConnectToFrontEnd();
  972. // client disconnected from nameserver after authentication
  973. // to switch to frontend
  974. return;
  975. case ChatState.Disconnecting:
  976. // expected disconnect
  977. break;
  978. default:
  979. // unexpected disconnect, we log warning and stacktrace
  980. string stacktrace = string.Empty;
  981. #if DEBUG && !NETFX_CORE
  982. stacktrace = new System.Diagnostics.StackTrace(true).ToString();
  983. #endif
  984. this.listener.DebugReturn(DebugLevel.WARNING, string.Format("Got a unexpected Disconnect in ChatState: {0}. Server: {1} Trace: {2}", this.State, this.chatPeer.ServerAddress, stacktrace));
  985. break;
  986. }
  987. if (this.AuthValues != null)
  988. {
  989. this.AuthValues.Token = null; // when leaving the server, invalidate the secret (but not the auth values)
  990. }
  991. this.State = ChatState.Disconnected;
  992. this.listener.OnChatStateChange(ChatState.Disconnected);
  993. this.listener.OnDisconnected();
  994. break;
  995. case StatusCode.DisconnectByServerUserLimit:
  996. this.listener.DebugReturn(DebugLevel.ERROR, "This connection was rejected due to the apps CCU limit.");
  997. this.Disconnect(ChatDisconnectCause.MaxCcuReached);
  998. break;
  999. case StatusCode.ExceptionOnConnect:
  1000. case StatusCode.SecurityExceptionOnConnect:
  1001. case StatusCode.EncryptionFailedToEstablish:
  1002. this.DisconnectedCause = ChatDisconnectCause.ExceptionOnConnect;
  1003. // if enabled, the client can attempt to connect with another networking-protocol to check if that connects
  1004. if (this.EnableProtocolFallback && this.State == ChatState.ConnectingToNameServer)
  1005. {
  1006. this.State = ChatState.ConnectWithFallbackProtocol;
  1007. }
  1008. else
  1009. {
  1010. this.Disconnect(ChatDisconnectCause.ExceptionOnConnect);
  1011. }
  1012. break;
  1013. case StatusCode.Exception:
  1014. case StatusCode.ExceptionOnReceive:
  1015. this.Disconnect(ChatDisconnectCause.Exception);
  1016. break;
  1017. case StatusCode.DisconnectByServerTimeout:
  1018. this.Disconnect(ChatDisconnectCause.ServerTimeout);
  1019. break;
  1020. case StatusCode.DisconnectByServerLogic:
  1021. this.Disconnect(ChatDisconnectCause.DisconnectByServerLogic);
  1022. break;
  1023. case StatusCode.DisconnectByServerReasonUnknown:
  1024. this.Disconnect(ChatDisconnectCause.DisconnectByServerReasonUnknown);
  1025. break;
  1026. case StatusCode.TimeoutDisconnect:
  1027. this.DisconnectedCause = ChatDisconnectCause.ClientTimeout;
  1028. // if enabled, the client can attempt to connect with another networking-protocol to check if that connects
  1029. if (this.EnableProtocolFallback && this.State == ChatState.ConnectingToNameServer)
  1030. {
  1031. this.State = ChatState.ConnectWithFallbackProtocol;
  1032. }
  1033. else
  1034. {
  1035. this.Disconnect(ChatDisconnectCause.ClientTimeout);
  1036. }
  1037. break;
  1038. }
  1039. }
  1040. #if SDK_V4
  1041. void IPhotonPeerListener.OnMessage(object msg)
  1042. {
  1043. string channelName = null;
  1044. var receivedBytes = (byte[])msg;
  1045. var channelId = BitConverter.ToInt32(receivedBytes, 0);
  1046. var messageBytes = new byte[receivedBytes.Length - 4];
  1047. Array.Copy(receivedBytes, 4, messageBytes, 0, receivedBytes.Length - 4);
  1048. foreach (var channel in this.PublicChannels)
  1049. {
  1050. if (channel.Value.ChannelID == channelId)
  1051. {
  1052. channelName = channel.Key;
  1053. break;
  1054. }
  1055. }
  1056. if (channelName != null)
  1057. {
  1058. this.listener.DebugReturn(DebugLevel.ALL, string.Format("got OnMessage in channel {0}", channelName));
  1059. }
  1060. else
  1061. {
  1062. this.listener.DebugReturn(DebugLevel.WARNING, string.Format("got OnMessage in unknown channel {0}", channelId));
  1063. }
  1064. this.listener.OnReceiveBroadcastMessage(channelName, messageBytes);
  1065. }
  1066. #endif
  1067. #endregion
  1068. private void TryAuthenticateOnNameServer()
  1069. {
  1070. if (!this.didAuthenticate)
  1071. {
  1072. this.didAuthenticate = this.chatPeer.AuthenticateOnNameServer(this.AppId, this.AppVersion, this.ChatRegion, this.AuthValues);
  1073. if (!this.didAuthenticate)
  1074. {
  1075. if (this.DebugOut >= DebugLevel.ERROR)
  1076. {
  1077. this.listener.DebugReturn(DebugLevel.ERROR, string.Format("Error calling OpAuthenticate! Did not work on NameServer. Check log output, AuthValues and if you're connected. State: {0}", this.State));
  1078. }
  1079. }
  1080. }
  1081. }
  1082. private bool SendChannelOperation(string[] channels, byte operation, int historyLength)
  1083. {
  1084. Dictionary<byte, object> opParameters = new Dictionary<byte, object> { { (byte)ChatParameterCode.Channels, channels } };
  1085. if (historyLength != 0)
  1086. {
  1087. opParameters.Add((byte)ChatParameterCode.HistoryLength, historyLength);
  1088. }
  1089. return this.chatPeer.SendOperation(operation, opParameters, SendOptions.SendReliable);
  1090. }
  1091. private void HandlePrivateMessageEvent(EventData eventData)
  1092. {
  1093. //Console.WriteLine(SupportClass.DictionaryToString(eventData.Parameters));
  1094. object message = (object)eventData.Parameters[(byte)ChatParameterCode.Message];
  1095. string sender = (string)eventData.Parameters[(byte)ChatParameterCode.Sender];
  1096. int msgId = (int)eventData.Parameters[ChatParameterCode.MsgId];
  1097. string channelName;
  1098. if (this.UserId != null && this.UserId.Equals(sender))
  1099. {
  1100. string target = (string)eventData.Parameters[(byte)ChatParameterCode.UserId];
  1101. channelName = this.GetPrivateChannelNameByUser(target);
  1102. }
  1103. else
  1104. {
  1105. channelName = this.GetPrivateChannelNameByUser(sender);
  1106. }
  1107. ChatChannel channel;
  1108. if (!this.PrivateChannels.TryGetValue(channelName, out channel))
  1109. {
  1110. channel = new ChatChannel(channelName);
  1111. channel.IsPrivate = true;
  1112. channel.MessageLimit = this.MessageLimit;
  1113. this.PrivateChannels.Add(channel.Name, channel);
  1114. }
  1115. channel.Add(sender, message, msgId);
  1116. this.listener.OnPrivateMessage(sender, message, channelName);
  1117. }
  1118. private void HandleChatMessagesEvent(EventData eventData)
  1119. {
  1120. object[] messages = (object[])eventData.Parameters[(byte)ChatParameterCode.Messages];
  1121. string[] senders = (string[])eventData.Parameters[(byte)ChatParameterCode.Senders];
  1122. string channelName = (string)eventData.Parameters[(byte)ChatParameterCode.Channel];
  1123. int lastMsgId = (int)eventData.Parameters[ChatParameterCode.MsgId];
  1124. ChatChannel channel;
  1125. if (!this.PublicChannels.TryGetValue(channelName, out channel))
  1126. {
  1127. if (this.DebugOut >= DebugLevel.WARNING)
  1128. {
  1129. this.listener.DebugReturn(DebugLevel.WARNING, "Channel " + channelName + " for incoming message event not found.");
  1130. }
  1131. return;
  1132. }
  1133. channel.Add(senders, messages, lastMsgId);
  1134. this.listener.OnGetMessages(channelName, senders, messages);
  1135. }
  1136. private void HandleSubscribeEvent(EventData eventData)
  1137. {
  1138. string[] channelsInResponse = (string[])eventData.Parameters[ChatParameterCode.Channels];
  1139. bool[] results = (bool[])eventData.Parameters[ChatParameterCode.SubscribeResults];
  1140. for (int i = 0; i < channelsInResponse.Length; i++)
  1141. {
  1142. if (results[i])
  1143. {
  1144. string channelName = channelsInResponse[i];
  1145. ChatChannel channel;
  1146. if (!this.PublicChannels.TryGetValue(channelName, out channel))
  1147. {
  1148. channel = new ChatChannel(channelName);
  1149. channel.MessageLimit = this.MessageLimit;
  1150. this.PublicChannels.Add(channel.Name, channel);
  1151. }
  1152. object temp;
  1153. if (eventData.Parameters.TryGetValue(ChatParameterCode.Properties, out temp))
  1154. {
  1155. Dictionary<object, object> channelProperties = temp as Dictionary<object, object>;
  1156. channel.ReadChannelProperties(channelProperties);
  1157. }
  1158. if (channel.PublishSubscribers) // or maybe remove check & always add anyway?
  1159. {
  1160. channel.Subscribers.Add(this.UserId);
  1161. }
  1162. if (eventData.Parameters.TryGetValue(ChatParameterCode.ChannelSubscribers, out temp))
  1163. {
  1164. string[] subscribers = temp as string[];
  1165. channel.AddSubscribers(subscribers);
  1166. }
  1167. #if CHAT_EXTENDED
  1168. if (eventData.Parameters.TryGetValue(ChatParameterCode.UserProperties, out temp))
  1169. {
  1170. Dictionary<string, Dictionary<object, object>> userProperties = temp as Dictionary<string, Dictionary<object, object>>;
  1171. foreach (var pair in userProperties)
  1172. {
  1173. channel.ReadUserProperties(pair.Key, pair.Value);
  1174. }
  1175. }
  1176. #endif
  1177. }
  1178. }
  1179. this.listener.OnSubscribed(channelsInResponse, results);
  1180. }
  1181. private void HandleUnsubscribeEvent(EventData eventData)
  1182. {
  1183. string[] channelsInRequest = (string[])eventData[ChatParameterCode.Channels];
  1184. for (int i = 0; i < channelsInRequest.Length; i++)
  1185. {
  1186. string channelName = channelsInRequest[i];
  1187. this.PublicChannels.Remove(channelName);
  1188. this.PublicChannelsUnsubscribing.Remove(channelName);
  1189. }
  1190. this.listener.OnUnsubscribed(channelsInRequest);
  1191. }
  1192. private void HandleAuthResponse(OperationResponse operationResponse)
  1193. {
  1194. if (this.DebugOut >= DebugLevel.INFO)
  1195. {
  1196. this.listener.DebugReturn(DebugLevel.INFO, operationResponse.ToStringFull() + " on: " + this.chatPeer.NameServerAddress);
  1197. }
  1198. if (operationResponse.ReturnCode == 0)
  1199. {
  1200. if (this.State == ChatState.ConnectedToNameServer)
  1201. {
  1202. this.State = ChatState.Authenticated;
  1203. this.listener.OnChatStateChange(this.State);
  1204. if (operationResponse.Parameters.ContainsKey(ParameterCode.Secret))
  1205. {
  1206. if (this.AuthValues == null)
  1207. {
  1208. this.AuthValues = new AuthenticationValues();
  1209. }
  1210. this.AuthValues.Token = operationResponse[ParameterCode.Secret] as string;
  1211. this.FrontendAddress = (string)operationResponse[ParameterCode.Address];
  1212. // we disconnect and status handler starts to connect to front end
  1213. this.chatPeer.Disconnect();
  1214. }
  1215. else
  1216. {
  1217. if (this.DebugOut >= DebugLevel.ERROR)
  1218. {
  1219. this.listener.DebugReturn(DebugLevel.ERROR, "No secret in authentication response.");
  1220. }
  1221. }
  1222. if (operationResponse.Parameters.ContainsKey(ParameterCode.UserId))
  1223. {
  1224. string incomingId = operationResponse.Parameters[ParameterCode.UserId] as string;
  1225. if (!string.IsNullOrEmpty(incomingId))
  1226. {
  1227. this.UserId = incomingId;
  1228. this.listener.DebugReturn(DebugLevel.INFO, string.Format("Received your UserID from server. Updating local value to: {0}", this.UserId));
  1229. }
  1230. }
  1231. }
  1232. else if (this.State == ChatState.ConnectingToFrontEnd)
  1233. {
  1234. this.State = ChatState.ConnectedToFrontEnd;
  1235. this.listener.OnChatStateChange(this.State);
  1236. this.listener.OnConnected();
  1237. if (statusToSetWhenConnected.HasValue)
  1238. {
  1239. SetOnlineStatus(statusToSetWhenConnected.Value, messageToSetWhenConnected);
  1240. statusToSetWhenConnected = null;
  1241. }
  1242. }
  1243. }
  1244. else
  1245. {
  1246. //this.listener.DebugReturn(DebugLevel.INFO, operationResponse.ToStringFull() + " NS: " + this.NameServerAddress + " FrontEnd: " + this.frontEndAddress);
  1247. switch (operationResponse.ReturnCode)
  1248. {
  1249. case ErrorCode.InvalidAuthentication:
  1250. this.DisconnectedCause = ChatDisconnectCause.InvalidAuthentication;
  1251. break;
  1252. case ErrorCode.CustomAuthenticationFailed:
  1253. this.DisconnectedCause = ChatDisconnectCause.CustomAuthenticationFailed;
  1254. break;
  1255. case ErrorCode.InvalidRegion:
  1256. this.DisconnectedCause = ChatDisconnectCause.InvalidRegion;
  1257. break;
  1258. case ErrorCode.MaxCcuReached:
  1259. this.DisconnectedCause = ChatDisconnectCause.MaxCcuReached;
  1260. break;
  1261. case ErrorCode.OperationNotAllowedInCurrentState:
  1262. this.DisconnectedCause = ChatDisconnectCause.OperationNotAllowedInCurrentState;
  1263. break;
  1264. case ErrorCode.AuthenticationTicketExpired:
  1265. this.DisconnectedCause = ChatDisconnectCause.AuthenticationTicketExpired;
  1266. break;
  1267. }
  1268. if (this.DebugOut >= DebugLevel.ERROR)
  1269. {
  1270. this.listener.DebugReturn(DebugLevel.ERROR, string.Format("{0} ClientState: {1} ServerAddress: {2}", operationResponse.ToStringFull(), this.State, this.chatPeer.ServerAddress));
  1271. }
  1272. this.Disconnect(this.DisconnectedCause);
  1273. }
  1274. }
  1275. private void HandleStatusUpdate(EventData eventData)
  1276. {
  1277. string user = (string)eventData.Parameters[ChatParameterCode.Sender];
  1278. int status = (int)eventData.Parameters[ChatParameterCode.Status];
  1279. object message = null;
  1280. bool gotMessage = eventData.Parameters.ContainsKey(ChatParameterCode.Message);
  1281. if (gotMessage)
  1282. {
  1283. message = eventData.Parameters[ChatParameterCode.Message];
  1284. }
  1285. this.listener.OnStatusUpdate(user, status, gotMessage, message);
  1286. }
  1287. private bool ConnectToFrontEnd()
  1288. {
  1289. this.State = ChatState.ConnectingToFrontEnd;
  1290. if (this.DebugOut >= DebugLevel.INFO)
  1291. {
  1292. this.listener.DebugReturn(DebugLevel.INFO, "Connecting to frontend " + this.FrontendAddress);
  1293. }
  1294. #if UNITY_WEBGL
  1295. if (this.TransportProtocol == ConnectionProtocol.Tcp || this.TransportProtocol == ConnectionProtocol.Udp)
  1296. {
  1297. this.listener.DebugReturn(DebugLevel.WARNING, "WebGL requires WebSockets. Switching TransportProtocol to WebSocketSecure.");
  1298. this.TransportProtocol = ConnectionProtocol.WebSocketSecure;
  1299. }
  1300. #endif
  1301. if (!this.chatPeer.Connect(this.FrontendAddress, ChatAppName))
  1302. {
  1303. if (this.DebugOut >= DebugLevel.ERROR)
  1304. {
  1305. this.listener.DebugReturn(DebugLevel.ERROR, string.Format("Connecting to frontend {0} failed.", this.FrontendAddress));
  1306. }
  1307. return false;
  1308. }
  1309. return true;
  1310. }
  1311. private bool AuthenticateOnFrontEnd()
  1312. {
  1313. if (this.AuthValues != null)
  1314. {
  1315. if (this.AuthValues.Token == null)
  1316. {
  1317. if (this.DebugOut >= DebugLevel.ERROR)
  1318. {
  1319. this.listener.DebugReturn(DebugLevel.ERROR, "Can't authenticate on front end server. Secret (AuthValues.Token) is not set");
  1320. }
  1321. return false;
  1322. }
  1323. else
  1324. {
  1325. Dictionary<byte, object> opParameters = new Dictionary<byte, object> { { (byte)ChatParameterCode.Secret, this.AuthValues.Token } };
  1326. if (this.PrivateChatHistoryLength > -1)
  1327. {
  1328. opParameters[(byte)ChatParameterCode.HistoryLength] = this.PrivateChatHistoryLength;
  1329. }
  1330. return this.chatPeer.SendOperation(ChatOperationCode.Authenticate, opParameters, SendOptions.SendReliable);
  1331. }
  1332. }
  1333. else
  1334. {
  1335. if (this.DebugOut >= DebugLevel.ERROR)
  1336. {
  1337. this.listener.DebugReturn(DebugLevel.ERROR, "Can't authenticate on front end server. Authentication Values are not set");
  1338. }
  1339. return false;
  1340. }
  1341. }
  1342. private void HandleUserUnsubscribedEvent(EventData eventData)
  1343. {
  1344. string channelName = eventData.Parameters[ChatParameterCode.Channel] as string;
  1345. string userId = eventData.Parameters[ChatParameterCode.UserId] as string;
  1346. ChatChannel channel;
  1347. if (this.PublicChannels.TryGetValue(channelName, out channel))
  1348. {
  1349. if (!channel.PublishSubscribers)
  1350. {
  1351. if (this.DebugOut >= DebugLevel.WARNING)
  1352. {
  1353. this.listener.DebugReturn(DebugLevel.WARNING, string.Format("Channel \"{0}\" for incoming UserUnsubscribed (\"{1}\") event does not have PublishSubscribers enabled.", channelName, userId));
  1354. }
  1355. }
  1356. if (!channel.Subscribers.Remove(userId)) // user not found!
  1357. {
  1358. if (this.DebugOut >= DebugLevel.WARNING)
  1359. {
  1360. this.listener.DebugReturn(DebugLevel.WARNING, string.Format("Channel \"{0}\" does not contain unsubscribed user \"{1}\".", channelName, userId));
  1361. }
  1362. }
  1363. }
  1364. else
  1365. {
  1366. if (this.DebugOut >= DebugLevel.WARNING)
  1367. {
  1368. this.listener.DebugReturn(DebugLevel.WARNING, string.Format("Channel \"{0}\" not found for incoming UserUnsubscribed (\"{1}\") event.", channelName, userId));
  1369. }
  1370. }
  1371. this.listener.OnUserUnsubscribed(channelName, userId);
  1372. }
  1373. private void HandleUserSubscribedEvent(EventData eventData)
  1374. {
  1375. //TODO: Handle user properties!
  1376. string channelName = eventData.Parameters[ChatParameterCode.Channel] as string;
  1377. string userId = eventData.Parameters[ChatParameterCode.UserId] as string;
  1378. ChatChannel channel;
  1379. if (this.PublicChannels.TryGetValue(channelName, out channel))
  1380. {
  1381. if (!channel.PublishSubscribers)
  1382. {
  1383. if (this.DebugOut >= DebugLevel.WARNING)
  1384. {
  1385. this.listener.DebugReturn(DebugLevel.WARNING, string.Format("Channel \"{0}\" for incoming UserSubscribed (\"{1}\") event does not have PublishSubscribers enabled.", channelName, userId));
  1386. }
  1387. }
  1388. if (!channel.Subscribers.Add(userId)) // user came back from the dead ?
  1389. {
  1390. if (this.DebugOut >= DebugLevel.WARNING)
  1391. {
  1392. this.listener.DebugReturn(DebugLevel.WARNING, string.Format("Channel \"{0}\" already contains newly subscribed user \"{1}\".", channelName, userId));
  1393. }
  1394. }
  1395. else if (channel.MaxSubscribers > 0 && channel.Subscribers.Count > channel.MaxSubscribers)
  1396. {
  1397. if (this.DebugOut >= DebugLevel.WARNING)
  1398. {
  1399. this.listener.DebugReturn(DebugLevel.WARNING, string.Format("Channel \"{0}\"'s MaxSubscribers exceeded. count={1} > MaxSubscribers={2}.", channelName, channel.Subscribers.Count, channel.MaxSubscribers));
  1400. }
  1401. }
  1402. }
  1403. else
  1404. {
  1405. if (this.DebugOut >= DebugLevel.WARNING)
  1406. {
  1407. this.listener.DebugReturn(DebugLevel.WARNING, string.Format("Channel \"{0}\" not found for incoming UserSubscribed (\"{1}\") event.", channelName, userId));
  1408. }
  1409. }
  1410. this.listener.OnUserSubscribed(channelName, userId);
  1411. }
  1412. #endregion
  1413. /// <summary>
  1414. /// Subscribe to a single channel and optionally sets its well-know channel properties in case the channel is created.
  1415. /// </summary>
  1416. /// <param name="channel">name of the channel to subscribe to</param>
  1417. /// <param name="lastMsgId">ID of the last received message from this channel when re subscribing to receive only missed messages, default is 0</param>
  1418. /// <param name="messagesFromHistory">how many missed messages to receive from history, default is -1 (available history). 0 will get you no items. Positive values are capped by a server side limit.</param>
  1419. /// <param name="creationOptions">options to be used in case the channel to subscribe to will be created.</param>
  1420. /// <returns></returns>
  1421. public bool Subscribe(string channel, int lastMsgId = 0, int messagesFromHistory = -1, ChannelCreationOptions creationOptions = null)
  1422. {
  1423. if (creationOptions == null)
  1424. {
  1425. creationOptions = ChannelCreationOptions.Default;
  1426. }
  1427. int maxSubscribers = creationOptions.MaxSubscribers;
  1428. bool publishSubscribers = creationOptions.PublishSubscribers;
  1429. if (maxSubscribers < 0)
  1430. {
  1431. if (this.DebugOut >= DebugLevel.ERROR)
  1432. {
  1433. this.listener.DebugReturn(DebugLevel.ERROR, "Cannot set MaxSubscribers < 0.");
  1434. }
  1435. return false;
  1436. }
  1437. if (lastMsgId < 0)
  1438. {
  1439. if (this.DebugOut >= DebugLevel.ERROR)
  1440. {
  1441. this.listener.DebugReturn(DebugLevel.ERROR, "lastMsgId cannot be < 0.");
  1442. }
  1443. return false;
  1444. }
  1445. if (messagesFromHistory < -1)
  1446. {
  1447. if (this.DebugOut >= DebugLevel.WARNING)
  1448. {
  1449. this.listener.DebugReturn(DebugLevel.WARNING, "messagesFromHistory < -1, setting it to -1");
  1450. }
  1451. messagesFromHistory = -1;
  1452. }
  1453. if (lastMsgId > 0 && messagesFromHistory == 0)
  1454. {
  1455. if (this.DebugOut >= DebugLevel.WARNING)
  1456. {
  1457. this.listener.DebugReturn(DebugLevel.WARNING, "lastMsgId will be ignored because messagesFromHistory == 0");
  1458. }
  1459. lastMsgId = 0;
  1460. }
  1461. Dictionary<object, object> properties = null;
  1462. if (publishSubscribers)
  1463. {
  1464. if (maxSubscribers > DefaultMaxSubscribers)
  1465. {
  1466. if (this.DebugOut >= DebugLevel.ERROR)
  1467. {
  1468. this.listener.DebugReturn(DebugLevel.ERROR,
  1469. string.Format("Cannot set MaxSubscribers > {0} when PublishSubscribers == true.", DefaultMaxSubscribers));
  1470. }
  1471. return false;
  1472. }
  1473. properties = new Dictionary<object, object>();
  1474. properties[ChannelWellKnownProperties.PublishSubscribers] = true;
  1475. }
  1476. if (maxSubscribers > 0)
  1477. {
  1478. if (properties == null)
  1479. {
  1480. properties = new Dictionary<object, object>();
  1481. }
  1482. properties[ChannelWellKnownProperties.MaxSubscribers] = maxSubscribers;
  1483. }
  1484. #if CHAT_EXTENDED
  1485. if (creationOptions.CustomProperties != null && creationOptions.CustomProperties.Count > 0)
  1486. {
  1487. foreach (var pair in creationOptions.CustomProperties)
  1488. {
  1489. properties.Add(pair.Key, pair.Value);
  1490. }
  1491. }
  1492. #endif
  1493. Dictionary<byte, object> opParameters = new Dictionary<byte, object> { { ChatParameterCode.Channels, new[] { channel } } };
  1494. if (messagesFromHistory != 0)
  1495. {
  1496. opParameters.Add(ChatParameterCode.HistoryLength, messagesFromHistory);
  1497. }
  1498. if (lastMsgId > 0)
  1499. {
  1500. opParameters.Add(ChatParameterCode.MsgIds, new[] { lastMsgId });
  1501. }
  1502. if (properties != null && properties.Count > 0)
  1503. {
  1504. opParameters.Add(ChatParameterCode.Properties, properties);
  1505. }
  1506. return this.chatPeer.SendOperation(ChatOperationCode.Subscribe, opParameters, SendOptions.SendReliable);
  1507. }
  1508. #if CHAT_EXTENDED
  1509. internal bool SetChannelProperties(string channelName, Dictionary<object, object> channelProperties, Dictionary<object, object> expectedProperties = null, bool httpForward = false)
  1510. {
  1511. if (!this.CanChat)
  1512. {
  1513. this.listener.DebugReturn(DebugLevel.ERROR, "SetChannelProperties called while not connected to front end server.");
  1514. return false;
  1515. }
  1516. if (string.IsNullOrEmpty(channelName) || channelProperties == null || channelProperties.Count == 0)
  1517. {
  1518. this.listener.DebugReturn(DebugLevel.WARNING, "SetChannelProperties parameters must be non-null and not empty.");
  1519. return false;
  1520. }
  1521. Dictionary<byte, object> parameters = new Dictionary<byte, object>
  1522. {
  1523. { ChatParameterCode.Channel, channelName },
  1524. { ChatParameterCode.Properties, channelProperties },
  1525. { ChatParameterCode.Broadcast, true }
  1526. };
  1527. if (httpForward)
  1528. {
  1529. parameters.Add(ChatParameterCode.WebFlags, HttpForwardWebFlag);
  1530. }
  1531. if (expectedProperties != null && expectedProperties.Count > 0)
  1532. {
  1533. parameters.Add(ChatParameterCode.ExpectedValues, expectedProperties);
  1534. }
  1535. return this.chatPeer.SendOperation(ChatOperationCode.SetProperties, parameters, SendOptions.SendReliable);
  1536. }
  1537. public bool SetCustomChannelProperties(string channelName, Dictionary<string, object> channelProperties, Dictionary<string, object> expectedProperties = null, bool httpForward = false)
  1538. {
  1539. if (channelProperties != null && channelProperties.Count > 0)
  1540. {
  1541. Dictionary<object, object> properties = new Dictionary<object, object>(channelProperties.Count);
  1542. foreach (var pair in channelProperties)
  1543. {
  1544. properties.Add(pair.Key, pair.Value);
  1545. }
  1546. Dictionary<object, object> expected = null;
  1547. if (expectedProperties != null && expectedProperties.Count > 0)
  1548. {
  1549. expected = new Dictionary<object, object>(expectedProperties.Count);
  1550. foreach (var pair in expectedProperties)
  1551. {
  1552. expected.Add(pair.Key, pair.Value);
  1553. }
  1554. }
  1555. return this.SetChannelProperties(channelName, properties, expected, httpForward);
  1556. }
  1557. return this.SetChannelProperties(channelName, null);
  1558. }
  1559. public bool SetCustomUserProperties(string channelName, string userId, Dictionary<string, object> userProperties, Dictionary<string, object> expectedProperties = null, bool httpForward = false)
  1560. {
  1561. if (userProperties != null && userProperties.Count > 0)
  1562. {
  1563. Dictionary<object, object> properties = new Dictionary<object, object>(userProperties.Count);
  1564. foreach (var pair in userProperties)
  1565. {
  1566. properties.Add(pair.Key, pair.Value);
  1567. }
  1568. Dictionary<object, object> expected = null;
  1569. if (expectedProperties != null && expectedProperties.Count > 0)
  1570. {
  1571. expected = new Dictionary<object, object>(expectedProperties.Count);
  1572. foreach (var pair in expectedProperties)
  1573. {
  1574. expected.Add(pair.Key, pair.Value);
  1575. }
  1576. }
  1577. return this.SetUserProperties(channelName, userId, properties, expected, httpForward);
  1578. }
  1579. return this.SetUserProperties(channelName, userId, null);
  1580. }
  1581. internal bool SetUserProperties(string channelName, string userId, Dictionary<object, object> channelProperties, Dictionary<object, object> expectedProperties = null, bool httpForward = false)
  1582. {
  1583. if (!this.CanChat)
  1584. {
  1585. this.listener.DebugReturn(DebugLevel.ERROR, "SetUserProperties called while not connected to front end server.");
  1586. return false;
  1587. }
  1588. if (string.IsNullOrEmpty(channelName))
  1589. {
  1590. this.listener.DebugReturn(DebugLevel.WARNING, "SetUserProperties \"channelName\" parameter must be non-null and not empty.");
  1591. return false;
  1592. }
  1593. if (channelProperties == null || channelProperties.Count == 0)
  1594. {
  1595. this.listener.DebugReturn(DebugLevel.WARNING, "SetUserProperties \"channelProperties\" parameter must be non-null and not empty.");
  1596. return false;
  1597. }
  1598. if (string.IsNullOrEmpty(userId))
  1599. {
  1600. this.listener.DebugReturn(DebugLevel.WARNING, "SetUserProperties \"userId\" parameter must be non-null and not empty.");
  1601. return false;
  1602. }
  1603. Dictionary<byte, object> parameters = new Dictionary<byte, object>
  1604. {
  1605. { ChatParameterCode.Channel, channelName },
  1606. { ChatParameterCode.Properties, channelProperties },
  1607. { ChatParameterCode.UserId, userId },
  1608. { ChatParameterCode.Broadcast, true }
  1609. };
  1610. if (httpForward)
  1611. {
  1612. parameters.Add(ChatParameterCode.WebFlags, HttpForwardWebFlag);
  1613. }
  1614. if (expectedProperties != null && expectedProperties.Count > 0)
  1615. {
  1616. parameters.Add(ChatParameterCode.ExpectedValues, expectedProperties);
  1617. }
  1618. return this.chatPeer.SendOperation(ChatOperationCode.SetProperties, parameters, SendOptions.SendReliable);
  1619. }
  1620. private void HandlePropertiesChanged(EventData eventData)
  1621. {
  1622. string channelName = eventData.Parameters[ChatParameterCode.Channel] as string;
  1623. ChatChannel channel;
  1624. if (!this.PublicChannels.TryGetValue(channelName, out channel))
  1625. {
  1626. this.listener.DebugReturn(DebugLevel.WARNING, string.Format("Channel {0} for incoming ChannelPropertiesUpdated event not found.", channelName));
  1627. return;
  1628. }
  1629. string senderId = eventData.Parameters[ChatParameterCode.Sender] as string;
  1630. Dictionary<object, object> changedProperties = eventData.Parameters[ChatParameterCode.Properties] as Dictionary<object, object>;
  1631. object temp;
  1632. if (eventData.Parameters.TryGetValue(ChatParameterCode.UserId, out temp))
  1633. {
  1634. string targetUserId = temp as string;
  1635. channel.ReadUserProperties(targetUserId, changedProperties);
  1636. this.listener.OnUserPropertiesChanged(channelName, targetUserId, senderId, changedProperties);
  1637. }
  1638. else
  1639. {
  1640. channel.ReadChannelProperties(changedProperties);
  1641. this.listener.OnChannelPropertiesChanged(channelName, senderId, changedProperties);
  1642. }
  1643. }
  1644. private void HandleErrorInfoEvent(EventData eventData)
  1645. {
  1646. string channel = eventData.Parameters[ChatParameterCode.Channel] as string;
  1647. string msg = eventData.Parameters[ChatParameterCode.DebugMessage] as string;
  1648. object data = eventData.Parameters[ChatParameterCode.DebugData];
  1649. this.listener.OnErrorInfo(channel, msg, data);
  1650. }
  1651. #endif
  1652. }
  1653. }