| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828 |
- // ----------------------------------------------------------------------------------------------------------------------
- // <summary>The Photon Chat Api enables clients to connect to a chat server and communicate with other clients.</summary>
- // <remarks>ChatClient is the main class of this api.</remarks>
- // <copyright company="Exit Games GmbH">Photon Chat Api - Copyright (C) 2014 Exit Games GmbH</copyright>
- // ----------------------------------------------------------------------------------------------------------------------
- #if UNITY_4_7 || UNITY_5 || UNITY_5_3_OR_NEWER
- #define SUPPORTED_UNITY
- #endif
- namespace Photon.Chat
- {
- using System;
- using System.Collections.Generic;
- using System.Diagnostics;
- using ExitGames.Client.Photon;
- #if SUPPORTED_UNITY || NETFX_CORE
- using Hashtable = ExitGames.Client.Photon.Hashtable;
- using SupportClass = ExitGames.Client.Photon.SupportClass;
- #endif
- /// <summary>Central class of the Photon Chat API to connect, handle channels and messages.</summary>
- /// <remarks>
- /// This class must be instantiated with a IChatClientListener instance to get the callbacks.
- /// Integrate it into your game loop by calling Service regularly. If the target platform supports Threads/Tasks,
- /// set UseBackgroundWorkerForSending = true, to let the ChatClient keep the connection by sending from
- /// an independent thread.
- ///
- /// Call Connect with an AppId that is setup as Photon Chat application. Note: Connect covers multiple
- /// messages between this client and the servers. A short workflow will connect you to a chat server.
- ///
- /// Each ChatClient resembles a user in chat (set in Connect). Each user automatically subscribes a channel
- /// for incoming private messages and can message any other user privately.
- /// Before you publish messages in any non-private channel, that channel must be subscribed.
- ///
- /// PublicChannels is a list of subscribed channels, containing messages and senders.
- /// PrivateChannels contains all incoming and sent private messages.
- /// </remarks>
- public class ChatClient : IPhotonPeerListener
- {
- const int FriendRequestListMax = 1024;
- /// <summary> Default maximum value possible for <see cref="ChatChannel.MaxSubscribers"/> when <see cref="ChatChannel.PublishSubscribers"/> is enabled</summary>
- public const int DefaultMaxSubscribers = 100;
- private const byte HttpForwardWebFlag = 0x01;
- /// <summary>Enables a fallback to another protocol in case a connect to the Name Server fails.</summary>
- /// <remarks>
- /// When connecting to the Name Server fails for a first time, the client will select an alternative
- /// network protocol and re-try to connect.
- ///
- /// The fallback will use the default Name Server port as defined by ProtocolToNameServerPort.
- ///
- /// The fallback for TCP is UDP. All other protocols fallback to TCP.
- /// </remarks>
- public bool EnableProtocolFallback { get; set; }
- /// <summary>The address of last connected Name Server.</summary>
- public string NameServerAddress { get; private set; }
- /// <summary>The address of the actual chat server assigned from NameServer. Public for read only.</summary>
- public string FrontendAddress { get; private set; }
- /// <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>
- private string chatRegion = "EU";
- /// <summary>Settable only before you connect! Defaults to "EU".</summary>
- public string ChatRegion
- {
- get { return this.chatRegion; }
- set { this.chatRegion = value; }
- }
- /// <summary>Current state of the ChatClient. Also use CanChat.</summary>
- public ChatState State { get; private set; }
- /// <summary> Disconnection cause. Check this inside <see cref="IChatClientListener.OnDisconnected"/>. </summary>
- public ChatDisconnectCause DisconnectedCause { get; private set; }
- /// <summary>
- /// Checks if this client is ready to send messages.
- /// </summary>
- public bool CanChat
- {
- get { return this.State == ChatState.ConnectedToFrontEnd && this.HasPeer; }
- }
- /// <summary>
- /// Checks if this client is ready to publish messages inside a public channel.
- /// </summary>
- /// <param name="channelName">The channel to do the check with.</param>
- /// <returns>Whether or not this client is ready to publish messages inside the public channel with the specified channelName.</returns>
- public bool CanChatInChannel(string channelName)
- {
- return this.CanChat && this.PublicChannels.ContainsKey(channelName) && !this.PublicChannelsUnsubscribing.Contains(channelName);
- }
- private bool HasPeer
- {
- get { return this.chatPeer != null; }
- }
- /// <summary>The version of your client. A new version also creates a new "virtual app" to separate players from older client versions.</summary>
- public string AppVersion { get; private set; }
- /// <summary>The AppID as assigned from the Photon Cloud.</summary>
- public string AppId { get; private set; }
- /// <summary>Settable only before you connect!</summary>
- public AuthenticationValues AuthValues { get; set; }
- /// <summary>The unique ID of a user/person, stored in AuthValues.UserId. Set it before you connect.</summary>
- /// <remarks>
- /// This value wraps AuthValues.UserId.
- /// It's not a nickname and we assume users with the same userID are the same person.</remarks>
- public string UserId
- {
- get
- {
- return (this.AuthValues != null) ? this.AuthValues.UserId : null;
- }
- private set
- {
- if (this.AuthValues == null)
- {
- this.AuthValues = new AuthenticationValues();
- }
- this.AuthValues.UserId = value;
- }
- }
- /// <summary>If greater than 0, new channels will limit the number of messages they cache locally.</summary>
- /// <remarks>
- /// This can be useful to limit the amount of memory used by chats.
- /// You can set a MessageLimit per channel but this value gets applied to new ones.
- ///
- /// Note:
- /// Changing this value, does not affect ChatChannels that are already in use!
- /// </remarks>
- public int MessageLimit;
- /// <summary>Limits the number of messages from private channel histories.</summary>
- /// <remarks>
- /// This is applied to all private channels on reconnect, as there is no explicit re-joining private channels.<br/>
- /// Default is -1, which gets available messages up to a maximum set by the server.<br/>
- /// A value of 0 gets you zero messages.<br/>
- /// The server's limit of messages may be lower. If so, the server's value will overrule this.<br/>
- /// </remarks>
- public int PrivateChatHistoryLength = -1;
- /// <summary> Public channels this client is subscribed to. </summary>
- public readonly Dictionary<string, ChatChannel> PublicChannels;
- /// <summary> Private channels in which this client has exchanged messages. </summary>
- public readonly Dictionary<string, ChatChannel> PrivateChannels;
- // channels being in unsubscribing process
- // items will be removed on successful unsubscription or subscription (the latter required after attempt to unsubscribe from not existing channel)
- private readonly HashSet<string> PublicChannelsUnsubscribing;
- private readonly IChatClientListener listener = null;
- /// <summary> The Chat Peer used by this client. </summary>
- public ChatPeer chatPeer = null;
- private const string ChatAppName = "chat";
- private bool didAuthenticate;
- private int? statusToSetWhenConnected;
- private object messageToSetWhenConnected;
- private int msDeltaForServiceCalls = 50;
- private int msTimestampOfLastServiceCall;
- /// <summary>Defines if a background thread will call SendOutgoingCommands, while your code calls Service to dispatch received messages.</summary>
- /// <remarks>
- /// The benefit of using a background thread to call SendOutgoingCommands is this:
- ///
- /// Even if your game logic is being paused, the background thread will keep the connection to the server up.
- /// On a lower level, acknowledgements and pings will prevent a server-side timeout while (e.g.) Unity loads assets.
- ///
- /// Your game logic still has to call Service regularly, or else incoming messages are not dispatched.
- /// As this typically triggers UI updates, it's easier to call Service from the main/UI thread.
- /// </remarks>
- public bool UseBackgroundWorkerForSending { get; set; }
- /// <summary>Exposes the TransportProtocol of the used PhotonPeer. Settable while not connected.</summary>
- public ConnectionProtocol TransportProtocol
- {
- get { return this.chatPeer.TransportProtocol; }
- set
- {
- if (this.chatPeer == null || this.chatPeer.PeerState != PeerStateValue.Disconnected)
- {
- this.listener.DebugReturn(DebugLevel.WARNING, "Can't set TransportProtocol. Disconnect first! " + ((this.chatPeer != null) ? "PeerState: " + this.chatPeer.PeerState : "The chatPeer is null."));
- return;
- }
- this.chatPeer.TransportProtocol = value;
- }
- }
- /// <summary>Defines which IPhotonSocket class to use per ConnectionProtocol.</summary>
- /// <remarks>
- /// Several platforms have special Socket implementations and slightly different APIs.
- /// To accomodate this, switching the socket implementation for a network protocol was made available.
- /// By default, UDP and TCP have socket implementations assigned.
- ///
- /// You only need to set the SocketImplementationConfig once, after creating a PhotonPeer
- /// and before connecting. If you switch the TransportProtocol, the correct implementation is being used.
- /// </remarks>
- public Dictionary<ConnectionProtocol, Type> SocketImplementationConfig
- {
- get { return this.chatPeer.SocketImplementationConfig; }
- }
- /// <summary>
- /// Chat client constructor.
- /// </summary>
- /// <param name="listener">The chat listener implementation.</param>
- /// <param name="protocol">Connection protocol to be used by this client. Default is <see cref="ConnectionProtocol.Udp"/>.</param>
- public ChatClient(IChatClientListener listener, ConnectionProtocol protocol = ConnectionProtocol.Udp)
- {
- this.listener = listener;
- this.State = ChatState.Uninitialized;
- this.chatPeer = new ChatPeer(this, protocol);
- this.chatPeer.SerializationProtocolType = SerializationProtocol.GpBinaryV18;
- this.PublicChannels = new Dictionary<string, ChatChannel>();
- this.PrivateChannels = new Dictionary<string, ChatChannel>();
- this.PublicChannelsUnsubscribing = new HashSet<string>();
- }
- public bool ConnectUsingSettings(ChatAppSettings appSettings)
- {
- if (appSettings == null)
- {
- this.listener.DebugReturn(DebugLevel.ERROR, "ConnectUsingSettings failed. The appSettings can't be null.'");
- return false;
- }
- if (!string.IsNullOrEmpty(appSettings.FixedRegion))
- {
- this.ChatRegion = appSettings.FixedRegion;
- }
- this.DebugOut = appSettings.NetworkLogging;
- this.TransportProtocol = appSettings.Protocol;
- this.EnableProtocolFallback = appSettings.EnableProtocolFallback;
- if (!appSettings.IsDefaultNameServer)
- {
- this.chatPeer.NameServerHost = appSettings.Server;
- this.chatPeer.NameServerPortOverride = appSettings.Port;
- }
- return this.Connect(appSettings.AppIdChat, appSettings.AppVersion, this.AuthValues);
- }
- /// <summary>
- /// Connects this client to the Photon Chat Cloud service, which will also authenticate the user (and set a UserId).
- /// </summary>
- /// <param name="appId">Get your Photon Chat AppId from the <a href="https://dashboard.photonengine.com">Dashboard</a>.</param>
- /// <param name="appVersion">Any version string you make up. Used to separate users and variants of your clients, which might be incompatible.</param>
- /// <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>
- /// <returns></returns>
- public bool Connect(string appId, string appVersion, AuthenticationValues authValues)
- {
- this.chatPeer.TimePingInterval = 3000;
- this.DisconnectedCause = ChatDisconnectCause.None;
- if (authValues != null)
- {
- this.AuthValues = authValues;
- }
- this.AppId = appId;
- this.AppVersion = appVersion;
- this.didAuthenticate = false;
- this.chatPeer.QuickResendAttempts = 2;
- this.chatPeer.SentCountAllowance = 7;
- // clean all channels
- this.PublicChannels.Clear();
- this.PrivateChannels.Clear();
- this.PublicChannelsUnsubscribing.Clear();
- #if UNITY_WEBGL
- if (this.TransportProtocol == ConnectionProtocol.Tcp || this.TransportProtocol == ConnectionProtocol.Udp)
- {
- this.listener.DebugReturn(DebugLevel.WARNING, "WebGL requires WebSockets. Switching TransportProtocol to WebSocketSecure.");
- this.TransportProtocol = ConnectionProtocol.WebSocketSecure;
- }
- #endif
- this.NameServerAddress = this.chatPeer.NameServerAddress;
- bool isConnecting = this.chatPeer.Connect();
- if (isConnecting)
- {
- this.State = ChatState.ConnectingToNameServer;
- }
- if (this.UseBackgroundWorkerForSending)
- {
- #if UNITY_SWITCH
- SupportClass.StartBackgroundCalls(this.SendOutgoingInBackground, this.msDeltaForServiceCalls); // as workaround, we don't name the Thread.
- #else
- SupportClass.StartBackgroundCalls(this.SendOutgoingInBackground, this.msDeltaForServiceCalls, "ChatClient Service Thread");
- #endif
- }
- return isConnecting;
- }
- /// <summary>
- /// Connects this client to the Photon Chat Cloud service, which will also authenticate the user (and set a UserId).
- /// This also sets an online status once connected. By default it will set user status to <see cref="ChatUserStatus.Online"/>.
- /// See <see cref="SetOnlineStatus(int,object)"/> for more information.
- /// </summary>
- /// <param name="appId">Get your Photon Chat AppId from the <a href="https://dashboard.photonengine.com">Dashboard</a>.</param>
- /// <param name="appVersion">Any version string you make up. Used to separate users and variants of your clients, which might be incompatible.</param>
- /// <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>
- /// <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>
- /// <param name="message">Optional status Also sets a status-message which your friends can get.</param>
- /// <returns>If the connection attempt could be sent at all.</returns>
- public bool ConnectAndSetStatus(string appId, string appVersion, AuthenticationValues authValues,
- int status = ChatUserStatus.Online, object message = null)
- {
- statusToSetWhenConnected = status;
- messageToSetWhenConnected = message;
- return Connect(appId, appVersion, authValues);
- }
- /// <summary>
- /// Must be called regularly to keep connection between client and server alive and to process incoming messages.
- /// </summary>
- /// <remarks>
- /// This method limits the effort it does automatically using the private variable msDeltaForServiceCalls.
- /// That value is lower for connect and multiplied by 4 when chat-server connection is ready.
- /// </remarks>
- public void Service()
- {
- // Dispatch until every already-received message got dispatched
- while (this.HasPeer && this.chatPeer.DispatchIncomingCommands())
- {
- }
- // if there is no background thread for sending, Service() will do that as well, in intervals
- if (!this.UseBackgroundWorkerForSending)
- {
- if (Environment.TickCount - this.msTimestampOfLastServiceCall > this.msDeltaForServiceCalls || this.msTimestampOfLastServiceCall == 0)
- {
- this.msTimestampOfLastServiceCall = Environment.TickCount;
- while (this.HasPeer && this.chatPeer.SendOutgoingCommands())
- {
- }
- }
- }
- }
- /// <summary>
- /// Called by a separate thread, this sends outgoing commands of this peer, as long as it's connected.
- /// </summary>
- /// <returns>True as long as the client is not disconnected.</returns>
- private bool SendOutgoingInBackground()
- {
- while (this.HasPeer && this.chatPeer.SendOutgoingCommands())
- {
- }
- return this.State != ChatState.Disconnected;
- }
- /// <summary> Obsolete: Better use UseBackgroundWorkerForSending and Service(). </summary>
- [Obsolete("Better use UseBackgroundWorkerForSending and Service().")]
- public void SendAcksOnly()
- {
- if (this.HasPeer) this.chatPeer.SendAcksOnly();
- }
- /// <summary>
- /// Disconnects from the Chat Server by sending a "disconnect command", which prevents a timeout server-side.
- /// </summary>
- public void Disconnect(ChatDisconnectCause cause = ChatDisconnectCause.DisconnectByClientLogic)
- {
- if (this.HasPeer && this.chatPeer.PeerState != PeerStateValue.Disconnected)
- {
- this.State = ChatState.Disconnecting;
- this.DisconnectedCause = cause;
- this.chatPeer.Disconnect();
- }
- }
- /// <summary>
- /// Locally shuts down the connection to the Chat Server. This resets states locally but the server will have to timeout this peer.
- /// </summary>
- public void StopThread()
- {
- if (this.HasPeer)
- {
- this.chatPeer.StopThread();
- }
- }
- /// <summary>Sends operation to subscribe to a list of channels by name.</summary>
- /// <param name="channels">List of channels to subscribe to. Avoid null or empty values.</param>
- /// <returns>If the operation could be sent at all (Example: Fails if not connected to Chat Server).</returns>
- public bool Subscribe(string[] channels)
- {
- return this.Subscribe(channels, 0);
- }
- /// <summary>
- /// Sends operation to subscribe to a list of channels by name and possibly retrieve messages we did not receive while unsubscribed.
- /// </summary>
- /// <param name="channels">List of channels to subscribe to. Avoid null or empty values.</param>
- /// <param name="lastMsgIds">ID of last message received per channel. Useful when re subscribing to receive only messages we missed.</param>
- /// <returns>If the operation could be sent at all (Example: Fails if not connected to Chat Server).</returns>
- public bool Subscribe(string[] channels, int[] lastMsgIds)
- {
- if (!this.CanChat)
- {
- if (this.DebugOut >= DebugLevel.ERROR)
- {
- this.listener.DebugReturn(DebugLevel.ERROR, "Subscribe called while not connected to front end server.");
- }
- return false;
- }
- if (channels == null || channels.Length == 0)
- {
- if (this.DebugOut >= DebugLevel.WARNING)
- {
- this.listener.DebugReturn(DebugLevel.WARNING, "Subscribe can't be called for empty or null channels-list.");
- }
- return false;
- }
- for (int i = 0; i < channels.Length; i++)
- {
- if (string.IsNullOrEmpty(channels[i]))
- {
- if (this.DebugOut >= DebugLevel.ERROR)
- {
- this.listener.DebugReturn(DebugLevel.ERROR, string.Format("Subscribe can't be called with a null or empty channel name at index {0}.", i));
- }
- return false;
- }
- }
- if (lastMsgIds == null || lastMsgIds.Length != channels.Length)
- {
- if (this.DebugOut >= DebugLevel.ERROR)
- {
- 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.");
- }
- return false;
- }
- Dictionary<byte, object> opParameters = new Dictionary<byte, object>
- {
- { ChatParameterCode.Channels, channels },
- { ChatParameterCode.MsgIds, lastMsgIds},
- { ChatParameterCode.HistoryLength, -1 } // server will decide how many messages to send to client
- };
- return this.chatPeer.SendOperation(ChatOperationCode.Subscribe, opParameters, SendOptions.SendReliable);
- }
- /// <summary>
- /// Sends operation to subscribe client to channels, optionally fetching a number of messages from the cache.
- /// </summary>
- /// <remarks>
- /// Subscribes channels will forward new messages to this user. Use PublishMessage to do so.
- /// The messages cache is limited but can be useful to get into ongoing conversations, if that's needed.
- /// </remarks>
- /// <param name="channels">List of channels to subscribe to. Avoid null or empty values.</param>
- /// <param name="messagesFromHistory">0: no history. 1 and higher: number of messages in history. -1: all available history.</param>
- /// <returns>If the operation could be sent at all (Example: Fails if not connected to Chat Server).</returns>
- public bool Subscribe(string[] channels, int messagesFromHistory)
- {
- if (!this.CanChat)
- {
- if (this.DebugOut >= DebugLevel.ERROR)
- {
- this.listener.DebugReturn(DebugLevel.ERROR, "Subscribe called while not connected to front end server.");
- }
- return false;
- }
- if (channels == null || channels.Length == 0)
- {
- if (this.DebugOut >= DebugLevel.WARNING)
- {
- this.listener.DebugReturn(DebugLevel.WARNING, "Subscribe can't be called for empty or null channels-list.");
- }
- return false;
- }
- return this.SendChannelOperation(channels, (byte)ChatOperationCode.Subscribe, messagesFromHistory);
- }
- /// <summary>Unsubscribes from a list of channels, which stops getting messages from those.</summary>
- /// <remarks>
- /// The client will remove these channels from the PublicChannels dictionary once the server sent a response to this request.
- ///
- /// The request will be sent to the server and IChatClientListener.OnUnsubscribed gets called when the server
- /// actually removed the channel subscriptions.
- ///
- /// Unsubscribe will fail if you include null or empty channel names.
- /// </remarks>
- /// <param name="channels">Names of channels to unsubscribe.</param>
- /// <returns>False, if not connected to a chat server.</returns>
- public bool Unsubscribe(string[] channels)
- {
- if (!this.CanChat)
- {
- if (this.DebugOut >= DebugLevel.ERROR)
- {
- this.listener.DebugReturn(DebugLevel.ERROR, "Unsubscribe called while not connected to front end server.");
- }
- return false;
- }
- if (channels == null || channels.Length == 0)
- {
- if (this.DebugOut >= DebugLevel.WARNING)
- {
- this.listener.DebugReturn(DebugLevel.WARNING, "Unsubscribe can't be called for empty or null channels-list.");
- }
- return false;
- }
- foreach (string ch in channels)
- {
- this.PublicChannelsUnsubscribing.Add(ch);
- }
- return this.SendChannelOperation(channels, ChatOperationCode.Unsubscribe, 0);
- }
- /// <summary>Sends a message to a public channel which this client subscribed to.</summary>
- /// <remarks>
- /// Before you publish to a channel, you have to subscribe it.
- /// Everyone in that channel will get the message.
- /// </remarks>
- /// <param name="channelName">Name of the channel to publish to.</param>
- /// <param name="message">Your message (string or any serializable data).</param>
- /// <param name="forwardAsWebhook">Optionally, public messages can be forwarded as webhooks. Configure webhooks for your Chat app to use this.</param>
- /// <returns>False if the client is not yet ready to send messages.</returns>
- public bool PublishMessage(string channelName, object message, bool forwardAsWebhook = false)
- {
- return this.publishMessage(channelName, message, true, forwardAsWebhook);
- }
- internal bool PublishMessageUnreliable(string channelName, object message, bool forwardAsWebhook = false)
- {
- return this.publishMessage(channelName, message, false, forwardAsWebhook);
- }
- private bool publishMessage(string channelName, object message, bool reliable, bool forwardAsWebhook = false)
- {
- if (!this.CanChat)
- {
- if (this.DebugOut >= DebugLevel.ERROR)
- {
- this.listener.DebugReturn(DebugLevel.ERROR, "PublishMessage called while not connected to front end server.");
- }
- return false;
- }
- if (string.IsNullOrEmpty(channelName) || message == null)
- {
- if (this.DebugOut >= DebugLevel.WARNING)
- {
- this.listener.DebugReturn(DebugLevel.WARNING, "PublishMessage parameters must be non-null and not empty.");
- }
- return false;
- }
- Dictionary<byte, object> parameters = new Dictionary<byte, object>
- {
- { (byte)ChatParameterCode.Channel, channelName },
- { (byte)ChatParameterCode.Message, message }
- };
- if (forwardAsWebhook)
- {
- parameters.Add(ChatParameterCode.WebFlags, (byte)0x1);
- }
- return this.chatPeer.SendOperation(ChatOperationCode.Publish, parameters, new SendOptions() { Reliability = reliable });
- }
- /// <summary>
- /// Sends a private message to a single target user. Calls OnPrivateMessage on the receiving client.
- /// </summary>
- /// <param name="target">Username to send this message to.</param>
- /// <param name="message">The message you want to send. Can be a simple string or anything serializable.</param>
- /// <param name="forwardAsWebhook">Optionally, private messages can be forwarded as webhooks. Configure webhooks for your Chat app to use this.</param>
- /// <returns>True if this clients can send the message to the server.</returns>
- public bool SendPrivateMessage(string target, object message, bool forwardAsWebhook = false)
- {
- return this.SendPrivateMessage(target, message, false, forwardAsWebhook);
- }
- /// <summary>
- /// Sends a private message to a single target user. Calls OnPrivateMessage on the receiving client.
- /// </summary>
- /// <param name="target">Username to send this message to.</param>
- /// <param name="message">The message you want to send. Can be a simple string or anything serializable.</param>
- /// <param name="encrypt">Optionally, private messages can be encrypted. Encryption is not end-to-end as the server decrypts the message.</param>
- /// <param name="forwardAsWebhook">Optionally, private messages can be forwarded as webhooks. Configure webhooks for your Chat app to use this.</param>
- /// <returns>True if this clients can send the message to the server.</returns>
- public bool SendPrivateMessage(string target, object message, bool encrypt, bool forwardAsWebhook)
- {
- return this.sendPrivateMessage(target, message, encrypt, true, forwardAsWebhook);
- }
- internal bool SendPrivateMessageUnreliable(string target, object message, bool encrypt, bool forwardAsWebhook = false)
- {
- return this.sendPrivateMessage(target, message, encrypt, false, forwardAsWebhook);
- }
- private bool sendPrivateMessage(string target, object message, bool encrypt, bool reliable, bool forwardAsWebhook = false)
- {
- if (!this.CanChat)
- {
- if (this.DebugOut >= DebugLevel.ERROR)
- {
- this.listener.DebugReturn(DebugLevel.ERROR, "SendPrivateMessage called while not connected to front end server.");
- }
- return false;
- }
- if (string.IsNullOrEmpty(target) || message == null)
- {
- if (this.DebugOut >= DebugLevel.WARNING)
- {
- this.listener.DebugReturn(DebugLevel.WARNING, "SendPrivateMessage parameters must be non-null and not empty.");
- }
- return false;
- }
- Dictionary<byte, object> parameters = new Dictionary<byte, object>
- {
- { ChatParameterCode.UserId, target },
- { ChatParameterCode.Message, message }
- };
- if (forwardAsWebhook)
- {
- parameters.Add(ChatParameterCode.WebFlags, (byte)0x1);
- }
- return this.chatPeer.SendOperation(ChatOperationCode.SendPrivate, parameters, new SendOptions() { Reliability = reliable, Encrypt = encrypt });
- }
- /// <summary>Sets the user's status (pre-defined or custom) and an optional message.</summary>
- /// <remarks>
- /// The predefined status values can be found in class ChatUserStatus.
- /// State ChatUserStatus.Invisible will make you offline for everyone and send no message.
- ///
- /// You can set custom values in the status integer. Aside from the pre-configured ones,
- /// all states will be considered visible and online. Else, no one would see the custom state.
- ///
- /// The message object can be anything that Photon can serialize, including (but not limited to)
- /// Hashtable, object[] and string. This value is defined by your own conventions.
- /// </remarks>
- /// <param name="status">Predefined states are in class ChatUserStatus. Other values can be used at will.</param>
- /// <param name="message">Optional string message or null.</param>
- /// <param name="skipMessage">If true, the message gets ignored. It can be null but won't replace any current message.</param>
- /// <returns>True if the operation gets called on the server.</returns>
- private bool SetOnlineStatus(int status, object message, bool skipMessage)
- {
- if (!this.CanChat)
- {
- if (this.DebugOut >= DebugLevel.ERROR)
- {
- this.listener.DebugReturn(DebugLevel.ERROR, "SetOnlineStatus called while not connected to front end server.");
- }
- return false;
- }
- Dictionary<byte, object> parameters = new Dictionary<byte, object>
- {
- { ChatParameterCode.Status, status },
- };
- if (skipMessage)
- {
- parameters[ChatParameterCode.SkipMessage] = true;
- }
- else
- {
- parameters[ChatParameterCode.Message] = message;
- }
- return this.chatPeer.SendOperation(ChatOperationCode.UpdateStatus, parameters, SendOptions.SendReliable);
- }
- /// <summary>Sets the user's status without changing your status-message.</summary>
- /// <remarks>
- /// The predefined status values can be found in class ChatUserStatus.
- /// State ChatUserStatus.Invisible will make you offline for everyone and send no message.
- ///
- /// You can set custom values in the status integer. Aside from the pre-configured ones,
- /// all states will be considered visible and online. Else, no one would see the custom state.
- ///
- /// This overload does not change the set message.
- /// </remarks>
- /// <param name="status">Predefined states are in class ChatUserStatus. Other values can be used at will.</param>
- /// <returns>True if the operation gets called on the server.</returns>
- public bool SetOnlineStatus(int status)
- {
- return this.SetOnlineStatus(status, null, true);
- }
- /// <summary>Sets the user's status without changing your status-message.</summary>
- /// <remarks>
- /// The predefined status values can be found in class ChatUserStatus.
- /// State ChatUserStatus.Invisible will make you offline for everyone and send no message.
- ///
- /// You can set custom values in the status integer. Aside from the pre-configured ones,
- /// all states will be considered visible and online. Else, no one would see the custom state.
- ///
- /// The message object can be anything that Photon can serialize, including (but not limited to)
- /// Hashtable, object[] and string. This value is defined by your own conventions.
- /// </remarks>
- /// <param name="status">Predefined states are in class ChatUserStatus. Other values can be used at will.</param>
- /// <param name="message">Also sets a status-message which your friends can get.</param>
- /// <returns>True if the operation gets called on the server.</returns>
- public bool SetOnlineStatus(int status, object message)
- {
- return this.SetOnlineStatus(status, message, false);
- }
- /// <summary>
- /// Adds friends to a list on the Chat Server which will send you status updates for those.
- /// </summary>
- /// <remarks>
- /// AddFriends and RemoveFriends enable clients to handle their friend list
- /// in the Photon Chat server. Having users on your friends list gives you access
- /// to their current online status (and whatever info your client sets in it).
- ///
- /// Each user can set an online status consisting of an integer and an arbitrary
- /// (serializable) object. The object can be null, Hashtable, object[] or anything
- /// else Photon can serialize.
- ///
- /// The status is published automatically to friends (anyone who set your user ID
- /// with AddFriends).
- ///
- /// Photon flushes friends-list when a chat client disconnects, so it has to be
- /// set each time. If your community API gives you access to online status already,
- /// you could filter and set online friends in AddFriends.
- ///
- /// Actual friend relations are not persistent and have to be stored outside
- /// of Photon.
- /// </remarks>
- /// <param name="friends">Array of friend userIds.</param>
- /// <returns>If the operation could be sent.</returns>
- public bool AddFriends(string[] friends)
- {
- if (!this.CanChat)
- {
- if (this.DebugOut >= DebugLevel.ERROR)
- {
- this.listener.DebugReturn(DebugLevel.ERROR, "AddFriends called while not connected to front end server.");
- }
- return false;
- }
- if (friends == null || friends.Length == 0)
- {
- if (this.DebugOut >= DebugLevel.WARNING)
- {
- this.listener.DebugReturn(DebugLevel.WARNING, "AddFriends can't be called for empty or null list.");
- }
- return false;
- }
- if (friends.Length > FriendRequestListMax)
- {
- if (this.DebugOut >= DebugLevel.WARNING)
- {
- this.listener.DebugReturn(DebugLevel.WARNING, "AddFriends max list size exceeded: " + friends.Length + " > " + FriendRequestListMax);
- }
- return false;
- }
- Dictionary<byte, object> parameters = new Dictionary<byte, object>
- {
- { ChatParameterCode.Friends, friends },
- };
- return this.chatPeer.SendOperation(ChatOperationCode.AddFriends, parameters, SendOptions.SendReliable);
- }
- /// <summary>
- /// Removes the provided entries from the list on the Chat Server and stops their status updates.
- /// </summary>
- /// <remarks>
- /// Photon flushes friends-list when a chat client disconnects. Unless you want to
- /// remove individual entries, you don't have to RemoveFriends.
- ///
- /// AddFriends and RemoveFriends enable clients to handle their friend list
- /// in the Photon Chat server. Having users on your friends list gives you access
- /// to their current online status (and whatever info your client sets in it).
- ///
- /// Each user can set an online status consisting of an integer and an arbitratry
- /// (serializable) object. The object can be null, Hashtable, object[] or anything
- /// else Photon can serialize.
- ///
- /// The status is published automatically to friends (anyone who set your user ID
- /// with AddFriends).
- ///
- /// Photon flushes friends-list when a chat client disconnects, so it has to be
- /// set each time. If your community API gives you access to online status already,
- /// you could filter and set online friends in AddFriends.
- ///
- /// Actual friend relations are not persistent and have to be stored outside
- /// of Photon.
- ///
- /// AddFriends and RemoveFriends enable clients to handle their friend list
- /// in the Photon Chat server. Having users on your friends list gives you access
- /// to their current online status (and whatever info your client sets in it).
- ///
- /// Each user can set an online status consisting of an integer and an arbitratry
- /// (serializable) object. The object can be null, Hashtable, object[] or anything
- /// else Photon can serialize.
- ///
- /// The status is published automatically to friends (anyone who set your user ID
- /// with AddFriends).
- ///
- ///
- /// Actual friend relations are not persistent and have to be stored outside
- /// of Photon.
- /// </remarks>
- /// <param name="friends">Array of friend userIds.</param>
- /// <returns>If the operation could be sent.</returns>
- public bool RemoveFriends(string[] friends)
- {
- if (!this.CanChat)
- {
- if (this.DebugOut >= DebugLevel.ERROR)
- {
- this.listener.DebugReturn(DebugLevel.ERROR, "RemoveFriends called while not connected to front end server.");
- }
- return false;
- }
- if (friends == null || friends.Length == 0)
- {
- if (this.DebugOut >= DebugLevel.WARNING)
- {
- this.listener.DebugReturn(DebugLevel.WARNING, "RemoveFriends can't be called for empty or null list.");
- }
- return false;
- }
- if (friends.Length > FriendRequestListMax)
- {
- if (this.DebugOut >= DebugLevel.WARNING)
- {
- this.listener.DebugReturn(DebugLevel.WARNING, "RemoveFriends max list size exceeded: " + friends.Length + " > " + FriendRequestListMax);
- }
- return false;
- }
- Dictionary<byte, object> parameters = new Dictionary<byte, object>
- {
- { ChatParameterCode.Friends, friends },
- };
- return this.chatPeer.SendOperation(ChatOperationCode.RemoveFriends, parameters, SendOptions.SendReliable);
- }
- /// <summary>
- /// Get you the (locally used) channel name for the chat between this client and another user.
- /// </summary>
- /// <param name="userName">Remote user's name or UserId.</param>
- /// <returns>The (locally used) channel name for a private channel.</returns>
- /// <remarks>Do not subscribe to this channel.
- /// Private channels do not need to be explicitly subscribed to.
- /// Use this for debugging purposes mainly.</remarks>
- public string GetPrivateChannelNameByUser(string userName)
- {
- return string.Format("{0}:{1}", this.UserId, userName);
- }
- /// <summary>
- /// Simplified access to either private or public channels by name.
- /// </summary>
- /// <param name="channelName">Name of the channel to get. For private channels, the channel-name is composed of both user's names.</param>
- /// <param name="isPrivate">Define if you expect a private or public channel.</param>
- /// <param name="channel">Out parameter gives you the found channel, if any.</param>
- /// <returns>True if the channel was found.</returns>
- /// <remarks>Public channels exist only when subscribed to them.
- /// Private channels exist only when at least one message is exchanged with the target user privately.</remarks>
- public bool TryGetChannel(string channelName, bool isPrivate, out ChatChannel channel)
- {
- if (!isPrivate)
- {
- return this.PublicChannels.TryGetValue(channelName, out channel);
- }
- else
- {
- return this.PrivateChannels.TryGetValue(channelName, out channel);
- }
- }
- /// <summary>
- /// Simplified access to all channels by name. Checks public channels first, then private ones.
- /// </summary>
- /// <param name="channelName">Name of the channel to get.</param>
- /// <param name="channel">Out parameter gives you the found channel, if any.</param>
- /// <returns>True if the channel was found.</returns>
- /// <remarks>Public channels exist only when subscribed to them.
- /// Private channels exist only when at least one message is exchanged with the target user privately.</remarks>
- public bool TryGetChannel(string channelName, out ChatChannel channel)
- {
- bool found = false;
- found = this.PublicChannels.TryGetValue(channelName, out channel);
- if (found) return true;
- found = this.PrivateChannels.TryGetValue(channelName, out channel);
- return found;
- }
- /// <summary>
- /// Simplified access to private channels by target user.
- /// </summary>
- /// <param name="userId">UserId of the target user in the private channel.</param>
- /// <param name="channel">Out parameter gives you the found channel, if any.</param>
- /// <returns>True if the channel was found.</returns>
- public bool TryGetPrivateChannelByUser(string userId, out ChatChannel channel)
- {
- channel = null;
- if (string.IsNullOrEmpty(userId))
- {
- return false;
- }
- string channelName = this.GetPrivateChannelNameByUser(userId);
- return this.TryGetChannel(channelName, true, out channel);
- }
- /// <summary>
- /// Sets the level (and amount) of debug output provided by the library.
- /// </summary>
- /// <remarks>
- /// This affects the callbacks to IChatClientListener.DebugReturn.
- /// Default Level: Error.
- /// </remarks>
- public DebugLevel DebugOut
- {
- set { this.chatPeer.DebugOut = value; }
- get { return this.chatPeer.DebugOut; }
- }
- #region Private methods area
- #region IPhotonPeerListener implementation
- void IPhotonPeerListener.DebugReturn(DebugLevel level, string message)
- {
- this.listener.DebugReturn(level, message);
- }
- void IPhotonPeerListener.OnEvent(EventData eventData)
- {
- switch (eventData.Code)
- {
- case ChatEventCode.ChatMessages:
- this.HandleChatMessagesEvent(eventData);
- break;
- case ChatEventCode.PrivateMessage:
- this.HandlePrivateMessageEvent(eventData);
- break;
- case ChatEventCode.StatusUpdate:
- this.HandleStatusUpdate(eventData);
- break;
- case ChatEventCode.Subscribe:
- this.HandleSubscribeEvent(eventData);
- break;
- case ChatEventCode.Unsubscribe:
- this.HandleUnsubscribeEvent(eventData);
- break;
- case ChatEventCode.UserSubscribed:
- this.HandleUserSubscribedEvent(eventData);
- break;
- case ChatEventCode.UserUnsubscribed:
- this.HandleUserUnsubscribedEvent(eventData);
- break;
- #if CHAT_EXTENDED
- case ChatEventCode.PropertiesChanged:
- this.HandlePropertiesChanged(eventData);
- break;
- case ChatEventCode.ErrorInfo:
- this.HandleErrorInfoEvent(eventData);
- break;
- #endif
- }
- }
- void IPhotonPeerListener.OnOperationResponse(OperationResponse operationResponse)
- {
- switch (operationResponse.OperationCode)
- {
- case (byte)ChatOperationCode.Authenticate:
- this.HandleAuthResponse(operationResponse);
- break;
- // the following operations usually don't return useful data and no error.
- case (byte)ChatOperationCode.Subscribe:
- case (byte)ChatOperationCode.Unsubscribe:
- case (byte)ChatOperationCode.Publish:
- case (byte)ChatOperationCode.SendPrivate:
- default:
- if ((operationResponse.ReturnCode != 0) && (this.DebugOut >= DebugLevel.ERROR))
- {
- if (operationResponse.ReturnCode == -2)
- {
- 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));
- }
- else
- {
- this.listener.DebugReturn(DebugLevel.ERROR, string.Format("Chat Operation {0} failed (Code: {1}). Debug Message: {2}", operationResponse.OperationCode, operationResponse.ReturnCode, operationResponse.DebugMessage));
- }
- }
- break;
- }
- }
- void IPhotonPeerListener.OnStatusChanged(StatusCode statusCode)
- {
- switch (statusCode)
- {
- case StatusCode.Connect:
- if (!this.chatPeer.IsProtocolSecure)
- {
- if (!this.chatPeer.EstablishEncryption())
- {
- if (this.DebugOut >= DebugLevel.ERROR)
- {
- this.listener.DebugReturn(DebugLevel.ERROR, "Error establishing encryption");
- }
- }
- }
- else
- {
- this.TryAuthenticateOnNameServer();
- }
- if (this.State == ChatState.ConnectingToNameServer)
- {
- this.State = ChatState.ConnectedToNameServer;
- this.listener.OnChatStateChange(this.State);
- }
- else if (this.State == ChatState.ConnectingToFrontEnd)
- {
- if (!this.AuthenticateOnFrontEnd())
- {
- if (this.DebugOut >= DebugLevel.ERROR)
- {
- this.listener.DebugReturn(DebugLevel.ERROR, string.Format("Error authenticating on frontend! Check log output, AuthValues and if you're connected. State: {0}", this.State));
- }
- }
- }
- break;
- case StatusCode.EncryptionEstablished:
- // once encryption is available, the client should send one (secure) authenticate. it includes the AppId (which identifies your app on the Photon Cloud)
- this.TryAuthenticateOnNameServer();
- break;
- case StatusCode.Disconnect:
- switch (this.State)
- {
- case ChatState.ConnectWithFallbackProtocol:
- this.EnableProtocolFallback = false; // the client does a fallback only one time
- this.chatPeer.NameServerPortOverride = 0; // resets a value in the peer only (as we change the protocol, the port has to change, too)
- this.chatPeer.TransportProtocol = (this.chatPeer.TransportProtocol == ConnectionProtocol.Tcp) ? ConnectionProtocol.Udp : ConnectionProtocol.Tcp;
- this.Connect(this.AppId, this.AppVersion, null);
- // the client now has to return, instead of break, to avoid further processing of the disconnect call
- return;
- case ChatState.Authenticated:
- this.ConnectToFrontEnd();
- // client disconnected from nameserver after authentication
- // to switch to frontend
- return;
- case ChatState.Disconnecting:
- // expected disconnect
- break;
- default:
- // unexpected disconnect, we log warning and stacktrace
- string stacktrace = string.Empty;
- #if DEBUG && !NETFX_CORE
- stacktrace = new System.Diagnostics.StackTrace(true).ToString();
- #endif
- this.listener.DebugReturn(DebugLevel.WARNING, string.Format("Got a unexpected Disconnect in ChatState: {0}. Server: {1} Trace: {2}", this.State, this.chatPeer.ServerAddress, stacktrace));
- break;
- }
- if (this.AuthValues != null)
- {
- this.AuthValues.Token = null; // when leaving the server, invalidate the secret (but not the auth values)
- }
- this.State = ChatState.Disconnected;
- this.listener.OnChatStateChange(ChatState.Disconnected);
- this.listener.OnDisconnected();
- break;
- case StatusCode.DisconnectByServerUserLimit:
- this.listener.DebugReturn(DebugLevel.ERROR, "This connection was rejected due to the apps CCU limit.");
- this.Disconnect(ChatDisconnectCause.MaxCcuReached);
- break;
- case StatusCode.ExceptionOnConnect:
- case StatusCode.SecurityExceptionOnConnect:
- case StatusCode.EncryptionFailedToEstablish:
- this.DisconnectedCause = ChatDisconnectCause.ExceptionOnConnect;
- // if enabled, the client can attempt to connect with another networking-protocol to check if that connects
- if (this.EnableProtocolFallback && this.State == ChatState.ConnectingToNameServer)
- {
- this.State = ChatState.ConnectWithFallbackProtocol;
- }
- else
- {
- this.Disconnect(ChatDisconnectCause.ExceptionOnConnect);
- }
- break;
- case StatusCode.Exception:
- case StatusCode.ExceptionOnReceive:
- this.Disconnect(ChatDisconnectCause.Exception);
- break;
- case StatusCode.DisconnectByServerTimeout:
- this.Disconnect(ChatDisconnectCause.ServerTimeout);
- break;
- case StatusCode.DisconnectByServerLogic:
- this.Disconnect(ChatDisconnectCause.DisconnectByServerLogic);
- break;
- case StatusCode.DisconnectByServerReasonUnknown:
- this.Disconnect(ChatDisconnectCause.DisconnectByServerReasonUnknown);
- break;
- case StatusCode.TimeoutDisconnect:
- this.DisconnectedCause = ChatDisconnectCause.ClientTimeout;
- // if enabled, the client can attempt to connect with another networking-protocol to check if that connects
- if (this.EnableProtocolFallback && this.State == ChatState.ConnectingToNameServer)
- {
- this.State = ChatState.ConnectWithFallbackProtocol;
- }
- else
- {
- this.Disconnect(ChatDisconnectCause.ClientTimeout);
- }
- break;
- }
- }
- #if SDK_V4
- void IPhotonPeerListener.OnMessage(object msg)
- {
- string channelName = null;
- var receivedBytes = (byte[])msg;
- var channelId = BitConverter.ToInt32(receivedBytes, 0);
- var messageBytes = new byte[receivedBytes.Length - 4];
- Array.Copy(receivedBytes, 4, messageBytes, 0, receivedBytes.Length - 4);
- foreach (var channel in this.PublicChannels)
- {
- if (channel.Value.ChannelID == channelId)
- {
- channelName = channel.Key;
- break;
- }
- }
- if (channelName != null)
- {
- this.listener.DebugReturn(DebugLevel.ALL, string.Format("got OnMessage in channel {0}", channelName));
- }
- else
- {
- this.listener.DebugReturn(DebugLevel.WARNING, string.Format("got OnMessage in unknown channel {0}", channelId));
- }
- this.listener.OnReceiveBroadcastMessage(channelName, messageBytes);
- }
- #endif
- #endregion
- private void TryAuthenticateOnNameServer()
- {
- if (!this.didAuthenticate)
- {
- this.didAuthenticate = this.chatPeer.AuthenticateOnNameServer(this.AppId, this.AppVersion, this.ChatRegion, this.AuthValues);
- if (!this.didAuthenticate)
- {
- if (this.DebugOut >= DebugLevel.ERROR)
- {
- 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));
- }
- }
- }
- }
- private bool SendChannelOperation(string[] channels, byte operation, int historyLength)
- {
- Dictionary<byte, object> opParameters = new Dictionary<byte, object> { { (byte)ChatParameterCode.Channels, channels } };
- if (historyLength != 0)
- {
- opParameters.Add((byte)ChatParameterCode.HistoryLength, historyLength);
- }
- return this.chatPeer.SendOperation(operation, opParameters, SendOptions.SendReliable);
- }
- private void HandlePrivateMessageEvent(EventData eventData)
- {
- //Console.WriteLine(SupportClass.DictionaryToString(eventData.Parameters));
- object message = (object)eventData.Parameters[(byte)ChatParameterCode.Message];
- string sender = (string)eventData.Parameters[(byte)ChatParameterCode.Sender];
- int msgId = (int)eventData.Parameters[ChatParameterCode.MsgId];
- string channelName;
- if (this.UserId != null && this.UserId.Equals(sender))
- {
- string target = (string)eventData.Parameters[(byte)ChatParameterCode.UserId];
- channelName = this.GetPrivateChannelNameByUser(target);
- }
- else
- {
- channelName = this.GetPrivateChannelNameByUser(sender);
- }
- ChatChannel channel;
- if (!this.PrivateChannels.TryGetValue(channelName, out channel))
- {
- channel = new ChatChannel(channelName);
- channel.IsPrivate = true;
- channel.MessageLimit = this.MessageLimit;
- this.PrivateChannels.Add(channel.Name, channel);
- }
- channel.Add(sender, message, msgId);
- this.listener.OnPrivateMessage(sender, message, channelName);
- }
- private void HandleChatMessagesEvent(EventData eventData)
- {
- object[] messages = (object[])eventData.Parameters[(byte)ChatParameterCode.Messages];
- string[] senders = (string[])eventData.Parameters[(byte)ChatParameterCode.Senders];
- string channelName = (string)eventData.Parameters[(byte)ChatParameterCode.Channel];
- int lastMsgId = (int)eventData.Parameters[ChatParameterCode.MsgId];
- ChatChannel channel;
- if (!this.PublicChannels.TryGetValue(channelName, out channel))
- {
- if (this.DebugOut >= DebugLevel.WARNING)
- {
- this.listener.DebugReturn(DebugLevel.WARNING, "Channel " + channelName + " for incoming message event not found.");
- }
- return;
- }
- channel.Add(senders, messages, lastMsgId);
- this.listener.OnGetMessages(channelName, senders, messages);
- }
- private void HandleSubscribeEvent(EventData eventData)
- {
- string[] channelsInResponse = (string[])eventData.Parameters[ChatParameterCode.Channels];
- bool[] results = (bool[])eventData.Parameters[ChatParameterCode.SubscribeResults];
- for (int i = 0; i < channelsInResponse.Length; i++)
- {
- if (results[i])
- {
- string channelName = channelsInResponse[i];
- ChatChannel channel;
- if (!this.PublicChannels.TryGetValue(channelName, out channel))
- {
- channel = new ChatChannel(channelName);
- channel.MessageLimit = this.MessageLimit;
- this.PublicChannels.Add(channel.Name, channel);
- }
- object temp;
- if (eventData.Parameters.TryGetValue(ChatParameterCode.Properties, out temp))
- {
- Dictionary<object, object> channelProperties = temp as Dictionary<object, object>;
- channel.ReadChannelProperties(channelProperties);
- }
- if (channel.PublishSubscribers) // or maybe remove check & always add anyway?
- {
- channel.Subscribers.Add(this.UserId);
- }
- if (eventData.Parameters.TryGetValue(ChatParameterCode.ChannelSubscribers, out temp))
- {
- string[] subscribers = temp as string[];
- channel.AddSubscribers(subscribers);
- }
- #if CHAT_EXTENDED
- if (eventData.Parameters.TryGetValue(ChatParameterCode.UserProperties, out temp))
- {
- Dictionary<string, Dictionary<object, object>> userProperties = temp as Dictionary<string, Dictionary<object, object>>;
- foreach (var pair in userProperties)
- {
- channel.ReadUserProperties(pair.Key, pair.Value);
- }
- }
- #endif
- }
- }
- this.listener.OnSubscribed(channelsInResponse, results);
- }
- private void HandleUnsubscribeEvent(EventData eventData)
- {
- string[] channelsInRequest = (string[])eventData[ChatParameterCode.Channels];
- for (int i = 0; i < channelsInRequest.Length; i++)
- {
- string channelName = channelsInRequest[i];
- this.PublicChannels.Remove(channelName);
- this.PublicChannelsUnsubscribing.Remove(channelName);
- }
- this.listener.OnUnsubscribed(channelsInRequest);
- }
- private void HandleAuthResponse(OperationResponse operationResponse)
- {
- if (this.DebugOut >= DebugLevel.INFO)
- {
- this.listener.DebugReturn(DebugLevel.INFO, operationResponse.ToStringFull() + " on: " + this.chatPeer.NameServerAddress);
- }
- if (operationResponse.ReturnCode == 0)
- {
- if (this.State == ChatState.ConnectedToNameServer)
- {
- this.State = ChatState.Authenticated;
- this.listener.OnChatStateChange(this.State);
- if (operationResponse.Parameters.ContainsKey(ParameterCode.Secret))
- {
- if (this.AuthValues == null)
- {
- this.AuthValues = new AuthenticationValues();
- }
- this.AuthValues.Token = operationResponse[ParameterCode.Secret] as string;
- this.FrontendAddress = (string)operationResponse[ParameterCode.Address];
- // we disconnect and status handler starts to connect to front end
- this.chatPeer.Disconnect();
- }
- else
- {
- if (this.DebugOut >= DebugLevel.ERROR)
- {
- this.listener.DebugReturn(DebugLevel.ERROR, "No secret in authentication response.");
- }
- }
- if (operationResponse.Parameters.ContainsKey(ParameterCode.UserId))
- {
- string incomingId = operationResponse.Parameters[ParameterCode.UserId] as string;
- if (!string.IsNullOrEmpty(incomingId))
- {
- this.UserId = incomingId;
- this.listener.DebugReturn(DebugLevel.INFO, string.Format("Received your UserID from server. Updating local value to: {0}", this.UserId));
- }
- }
- }
- else if (this.State == ChatState.ConnectingToFrontEnd)
- {
- this.State = ChatState.ConnectedToFrontEnd;
- this.listener.OnChatStateChange(this.State);
- this.listener.OnConnected();
- if (statusToSetWhenConnected.HasValue)
- {
- SetOnlineStatus(statusToSetWhenConnected.Value, messageToSetWhenConnected);
- statusToSetWhenConnected = null;
- }
- }
- }
- else
- {
- //this.listener.DebugReturn(DebugLevel.INFO, operationResponse.ToStringFull() + " NS: " + this.NameServerAddress + " FrontEnd: " + this.frontEndAddress);
- switch (operationResponse.ReturnCode)
- {
- case ErrorCode.InvalidAuthentication:
- this.DisconnectedCause = ChatDisconnectCause.InvalidAuthentication;
- break;
- case ErrorCode.CustomAuthenticationFailed:
- this.DisconnectedCause = ChatDisconnectCause.CustomAuthenticationFailed;
- break;
- case ErrorCode.InvalidRegion:
- this.DisconnectedCause = ChatDisconnectCause.InvalidRegion;
- break;
- case ErrorCode.MaxCcuReached:
- this.DisconnectedCause = ChatDisconnectCause.MaxCcuReached;
- break;
- case ErrorCode.OperationNotAllowedInCurrentState:
- this.DisconnectedCause = ChatDisconnectCause.OperationNotAllowedInCurrentState;
- break;
- case ErrorCode.AuthenticationTicketExpired:
- this.DisconnectedCause = ChatDisconnectCause.AuthenticationTicketExpired;
- break;
- }
- if (this.DebugOut >= DebugLevel.ERROR)
- {
- this.listener.DebugReturn(DebugLevel.ERROR, string.Format("{0} ClientState: {1} ServerAddress: {2}", operationResponse.ToStringFull(), this.State, this.chatPeer.ServerAddress));
- }
- this.Disconnect(this.DisconnectedCause);
- }
- }
- private void HandleStatusUpdate(EventData eventData)
- {
- string user = (string)eventData.Parameters[ChatParameterCode.Sender];
- int status = (int)eventData.Parameters[ChatParameterCode.Status];
- object message = null;
- bool gotMessage = eventData.Parameters.ContainsKey(ChatParameterCode.Message);
- if (gotMessage)
- {
- message = eventData.Parameters[ChatParameterCode.Message];
- }
- this.listener.OnStatusUpdate(user, status, gotMessage, message);
- }
- private bool ConnectToFrontEnd()
- {
- this.State = ChatState.ConnectingToFrontEnd;
- if (this.DebugOut >= DebugLevel.INFO)
- {
- this.listener.DebugReturn(DebugLevel.INFO, "Connecting to frontend " + this.FrontendAddress);
- }
- #if UNITY_WEBGL
- if (this.TransportProtocol == ConnectionProtocol.Tcp || this.TransportProtocol == ConnectionProtocol.Udp)
- {
- this.listener.DebugReturn(DebugLevel.WARNING, "WebGL requires WebSockets. Switching TransportProtocol to WebSocketSecure.");
- this.TransportProtocol = ConnectionProtocol.WebSocketSecure;
- }
- #endif
- if (!this.chatPeer.Connect(this.FrontendAddress, ChatAppName))
- {
- if (this.DebugOut >= DebugLevel.ERROR)
- {
- this.listener.DebugReturn(DebugLevel.ERROR, string.Format("Connecting to frontend {0} failed.", this.FrontendAddress));
- }
- return false;
- }
- return true;
- }
- private bool AuthenticateOnFrontEnd()
- {
- if (this.AuthValues != null)
- {
- if (this.AuthValues.Token == null)
- {
- if (this.DebugOut >= DebugLevel.ERROR)
- {
- this.listener.DebugReturn(DebugLevel.ERROR, "Can't authenticate on front end server. Secret (AuthValues.Token) is not set");
- }
- return false;
- }
- else
- {
- Dictionary<byte, object> opParameters = new Dictionary<byte, object> { { (byte)ChatParameterCode.Secret, this.AuthValues.Token } };
- if (this.PrivateChatHistoryLength > -1)
- {
- opParameters[(byte)ChatParameterCode.HistoryLength] = this.PrivateChatHistoryLength;
- }
- return this.chatPeer.SendOperation(ChatOperationCode.Authenticate, opParameters, SendOptions.SendReliable);
- }
- }
- else
- {
- if (this.DebugOut >= DebugLevel.ERROR)
- {
- this.listener.DebugReturn(DebugLevel.ERROR, "Can't authenticate on front end server. Authentication Values are not set");
- }
- return false;
- }
- }
- private void HandleUserUnsubscribedEvent(EventData eventData)
- {
- string channelName = eventData.Parameters[ChatParameterCode.Channel] as string;
- string userId = eventData.Parameters[ChatParameterCode.UserId] as string;
- ChatChannel channel;
- if (this.PublicChannels.TryGetValue(channelName, out channel))
- {
- if (!channel.PublishSubscribers)
- {
- if (this.DebugOut >= DebugLevel.WARNING)
- {
- this.listener.DebugReturn(DebugLevel.WARNING, string.Format("Channel \"{0}\" for incoming UserUnsubscribed (\"{1}\") event does not have PublishSubscribers enabled.", channelName, userId));
- }
- }
- if (!channel.Subscribers.Remove(userId)) // user not found!
- {
- if (this.DebugOut >= DebugLevel.WARNING)
- {
- this.listener.DebugReturn(DebugLevel.WARNING, string.Format("Channel \"{0}\" does not contain unsubscribed user \"{1}\".", channelName, userId));
- }
- }
- }
- else
- {
- if (this.DebugOut >= DebugLevel.WARNING)
- {
- this.listener.DebugReturn(DebugLevel.WARNING, string.Format("Channel \"{0}\" not found for incoming UserUnsubscribed (\"{1}\") event.", channelName, userId));
- }
- }
- this.listener.OnUserUnsubscribed(channelName, userId);
- }
- private void HandleUserSubscribedEvent(EventData eventData)
- {
- //TODO: Handle user properties!
- string channelName = eventData.Parameters[ChatParameterCode.Channel] as string;
- string userId = eventData.Parameters[ChatParameterCode.UserId] as string;
- ChatChannel channel;
- if (this.PublicChannels.TryGetValue(channelName, out channel))
- {
- if (!channel.PublishSubscribers)
- {
- if (this.DebugOut >= DebugLevel.WARNING)
- {
- this.listener.DebugReturn(DebugLevel.WARNING, string.Format("Channel \"{0}\" for incoming UserSubscribed (\"{1}\") event does not have PublishSubscribers enabled.", channelName, userId));
- }
- }
- if (!channel.Subscribers.Add(userId)) // user came back from the dead ?
- {
- if (this.DebugOut >= DebugLevel.WARNING)
- {
- this.listener.DebugReturn(DebugLevel.WARNING, string.Format("Channel \"{0}\" already contains newly subscribed user \"{1}\".", channelName, userId));
- }
- }
- else if (channel.MaxSubscribers > 0 && channel.Subscribers.Count > channel.MaxSubscribers)
- {
- if (this.DebugOut >= DebugLevel.WARNING)
- {
- this.listener.DebugReturn(DebugLevel.WARNING, string.Format("Channel \"{0}\"'s MaxSubscribers exceeded. count={1} > MaxSubscribers={2}.", channelName, channel.Subscribers.Count, channel.MaxSubscribers));
- }
- }
- }
- else
- {
- if (this.DebugOut >= DebugLevel.WARNING)
- {
- this.listener.DebugReturn(DebugLevel.WARNING, string.Format("Channel \"{0}\" not found for incoming UserSubscribed (\"{1}\") event.", channelName, userId));
- }
- }
- this.listener.OnUserSubscribed(channelName, userId);
- }
- #endregion
- /// <summary>
- /// Subscribe to a single channel and optionally sets its well-know channel properties in case the channel is created.
- /// </summary>
- /// <param name="channel">name of the channel to subscribe to</param>
- /// <param name="lastMsgId">ID of the last received message from this channel when re subscribing to receive only missed messages, default is 0</param>
- /// <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>
- /// <param name="creationOptions">options to be used in case the channel to subscribe to will be created.</param>
- /// <returns></returns>
- public bool Subscribe(string channel, int lastMsgId = 0, int messagesFromHistory = -1, ChannelCreationOptions creationOptions = null)
- {
- if (creationOptions == null)
- {
- creationOptions = ChannelCreationOptions.Default;
- }
- int maxSubscribers = creationOptions.MaxSubscribers;
- bool publishSubscribers = creationOptions.PublishSubscribers;
- if (maxSubscribers < 0)
- {
- if (this.DebugOut >= DebugLevel.ERROR)
- {
- this.listener.DebugReturn(DebugLevel.ERROR, "Cannot set MaxSubscribers < 0.");
- }
- return false;
- }
- if (lastMsgId < 0)
- {
- if (this.DebugOut >= DebugLevel.ERROR)
- {
- this.listener.DebugReturn(DebugLevel.ERROR, "lastMsgId cannot be < 0.");
- }
- return false;
- }
- if (messagesFromHistory < -1)
- {
- if (this.DebugOut >= DebugLevel.WARNING)
- {
- this.listener.DebugReturn(DebugLevel.WARNING, "messagesFromHistory < -1, setting it to -1");
- }
- messagesFromHistory = -1;
- }
- if (lastMsgId > 0 && messagesFromHistory == 0)
- {
- if (this.DebugOut >= DebugLevel.WARNING)
- {
- this.listener.DebugReturn(DebugLevel.WARNING, "lastMsgId will be ignored because messagesFromHistory == 0");
- }
- lastMsgId = 0;
- }
- Dictionary<object, object> properties = null;
- if (publishSubscribers)
- {
- if (maxSubscribers > DefaultMaxSubscribers)
- {
- if (this.DebugOut >= DebugLevel.ERROR)
- {
- this.listener.DebugReturn(DebugLevel.ERROR,
- string.Format("Cannot set MaxSubscribers > {0} when PublishSubscribers == true.", DefaultMaxSubscribers));
- }
- return false;
- }
- properties = new Dictionary<object, object>();
- properties[ChannelWellKnownProperties.PublishSubscribers] = true;
- }
- if (maxSubscribers > 0)
- {
- if (properties == null)
- {
- properties = new Dictionary<object, object>();
- }
- properties[ChannelWellKnownProperties.MaxSubscribers] = maxSubscribers;
- }
- #if CHAT_EXTENDED
- if (creationOptions.CustomProperties != null && creationOptions.CustomProperties.Count > 0)
- {
- foreach (var pair in creationOptions.CustomProperties)
- {
- properties.Add(pair.Key, pair.Value);
- }
- }
- #endif
- Dictionary<byte, object> opParameters = new Dictionary<byte, object> { { ChatParameterCode.Channels, new[] { channel } } };
- if (messagesFromHistory != 0)
- {
- opParameters.Add(ChatParameterCode.HistoryLength, messagesFromHistory);
- }
- if (lastMsgId > 0)
- {
- opParameters.Add(ChatParameterCode.MsgIds, new[] { lastMsgId });
- }
- if (properties != null && properties.Count > 0)
- {
- opParameters.Add(ChatParameterCode.Properties, properties);
- }
- return this.chatPeer.SendOperation(ChatOperationCode.Subscribe, opParameters, SendOptions.SendReliable);
- }
- #if CHAT_EXTENDED
- internal bool SetChannelProperties(string channelName, Dictionary<object, object> channelProperties, Dictionary<object, object> expectedProperties = null, bool httpForward = false)
- {
- if (!this.CanChat)
- {
- this.listener.DebugReturn(DebugLevel.ERROR, "SetChannelProperties called while not connected to front end server.");
- return false;
- }
- if (string.IsNullOrEmpty(channelName) || channelProperties == null || channelProperties.Count == 0)
- {
- this.listener.DebugReturn(DebugLevel.WARNING, "SetChannelProperties parameters must be non-null and not empty.");
- return false;
- }
- Dictionary<byte, object> parameters = new Dictionary<byte, object>
- {
- { ChatParameterCode.Channel, channelName },
- { ChatParameterCode.Properties, channelProperties },
- { ChatParameterCode.Broadcast, true }
- };
- if (httpForward)
- {
- parameters.Add(ChatParameterCode.WebFlags, HttpForwardWebFlag);
- }
- if (expectedProperties != null && expectedProperties.Count > 0)
- {
- parameters.Add(ChatParameterCode.ExpectedValues, expectedProperties);
- }
- return this.chatPeer.SendOperation(ChatOperationCode.SetProperties, parameters, SendOptions.SendReliable);
- }
- public bool SetCustomChannelProperties(string channelName, Dictionary<string, object> channelProperties, Dictionary<string, object> expectedProperties = null, bool httpForward = false)
- {
- if (channelProperties != null && channelProperties.Count > 0)
- {
- Dictionary<object, object> properties = new Dictionary<object, object>(channelProperties.Count);
- foreach (var pair in channelProperties)
- {
- properties.Add(pair.Key, pair.Value);
- }
- Dictionary<object, object> expected = null;
- if (expectedProperties != null && expectedProperties.Count > 0)
- {
- expected = new Dictionary<object, object>(expectedProperties.Count);
- foreach (var pair in expectedProperties)
- {
- expected.Add(pair.Key, pair.Value);
- }
- }
- return this.SetChannelProperties(channelName, properties, expected, httpForward);
- }
- return this.SetChannelProperties(channelName, null);
- }
- public bool SetCustomUserProperties(string channelName, string userId, Dictionary<string, object> userProperties, Dictionary<string, object> expectedProperties = null, bool httpForward = false)
- {
- if (userProperties != null && userProperties.Count > 0)
- {
- Dictionary<object, object> properties = new Dictionary<object, object>(userProperties.Count);
- foreach (var pair in userProperties)
- {
- properties.Add(pair.Key, pair.Value);
- }
- Dictionary<object, object> expected = null;
- if (expectedProperties != null && expectedProperties.Count > 0)
- {
- expected = new Dictionary<object, object>(expectedProperties.Count);
- foreach (var pair in expectedProperties)
- {
- expected.Add(pair.Key, pair.Value);
- }
- }
- return this.SetUserProperties(channelName, userId, properties, expected, httpForward);
- }
- return this.SetUserProperties(channelName, userId, null);
- }
- internal bool SetUserProperties(string channelName, string userId, Dictionary<object, object> channelProperties, Dictionary<object, object> expectedProperties = null, bool httpForward = false)
- {
- if (!this.CanChat)
- {
- this.listener.DebugReturn(DebugLevel.ERROR, "SetUserProperties called while not connected to front end server.");
- return false;
- }
- if (string.IsNullOrEmpty(channelName))
- {
- this.listener.DebugReturn(DebugLevel.WARNING, "SetUserProperties \"channelName\" parameter must be non-null and not empty.");
- return false;
- }
- if (channelProperties == null || channelProperties.Count == 0)
- {
- this.listener.DebugReturn(DebugLevel.WARNING, "SetUserProperties \"channelProperties\" parameter must be non-null and not empty.");
- return false;
- }
- if (string.IsNullOrEmpty(userId))
- {
- this.listener.DebugReturn(DebugLevel.WARNING, "SetUserProperties \"userId\" parameter must be non-null and not empty.");
- return false;
- }
- Dictionary<byte, object> parameters = new Dictionary<byte, object>
- {
- { ChatParameterCode.Channel, channelName },
- { ChatParameterCode.Properties, channelProperties },
- { ChatParameterCode.UserId, userId },
- { ChatParameterCode.Broadcast, true }
- };
- if (httpForward)
- {
- parameters.Add(ChatParameterCode.WebFlags, HttpForwardWebFlag);
- }
- if (expectedProperties != null && expectedProperties.Count > 0)
- {
- parameters.Add(ChatParameterCode.ExpectedValues, expectedProperties);
- }
- return this.chatPeer.SendOperation(ChatOperationCode.SetProperties, parameters, SendOptions.SendReliable);
- }
- private void HandlePropertiesChanged(EventData eventData)
- {
- string channelName = eventData.Parameters[ChatParameterCode.Channel] as string;
- ChatChannel channel;
- if (!this.PublicChannels.TryGetValue(channelName, out channel))
- {
- this.listener.DebugReturn(DebugLevel.WARNING, string.Format("Channel {0} for incoming ChannelPropertiesUpdated event not found.", channelName));
- return;
- }
- string senderId = eventData.Parameters[ChatParameterCode.Sender] as string;
- Dictionary<object, object> changedProperties = eventData.Parameters[ChatParameterCode.Properties] as Dictionary<object, object>;
- object temp;
- if (eventData.Parameters.TryGetValue(ChatParameterCode.UserId, out temp))
- {
- string targetUserId = temp as string;
- channel.ReadUserProperties(targetUserId, changedProperties);
- this.listener.OnUserPropertiesChanged(channelName, targetUserId, senderId, changedProperties);
- }
- else
- {
- channel.ReadChannelProperties(changedProperties);
- this.listener.OnChannelPropertiesChanged(channelName, senderId, changedProperties);
- }
- }
- private void HandleErrorInfoEvent(EventData eventData)
- {
- string channel = eventData.Parameters[ChatParameterCode.Channel] as string;
- string msg = eventData.Parameters[ChatParameterCode.DebugMessage] as string;
- object data = eventData.Parameters[ChatParameterCode.DebugData];
- this.listener.OnErrorInfo(channel, msg, data);
- }
- #endif
- }
- }
|