ChatPeer.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  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.Diagnostics;
  13. using System.Collections.Generic;
  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>
  20. /// Provides basic operations of the Photon Chat server. This internal class is used by public ChatClient.
  21. /// </summary>
  22. public class ChatPeer : PhotonPeer
  23. {
  24. /// <summary>Name Server Host Name for Photon Cloud. Without port and without any prefix.</summary>
  25. public string NameServerHost = "ns.photonengine.io";
  26. /// <summary>Name Server port per protocol (the UDP port is different than TCP, etc).</summary>
  27. private static readonly Dictionary<ConnectionProtocol, int> ProtocolToNameServerPort = new Dictionary<ConnectionProtocol, int>() { { ConnectionProtocol.Udp, 5058 }, { ConnectionProtocol.Tcp, 4533 }, { ConnectionProtocol.WebSocket, 9093 }, { ConnectionProtocol.WebSocketSecure, 19093 } }; //, { ConnectionProtocol.RHttp, 6063 } };
  28. /// <summary>Name Server Address for Photon Cloud (based on current protocol). You can use the default values and usually won't have to set this value.</summary>
  29. public string NameServerAddress { get { return this.GetNameServerAddress(); } }
  30. virtual internal bool IsProtocolSecure { get { return this.UsedProtocol == ConnectionProtocol.WebSocketSecure; } }
  31. /// <summary> Chat Peer constructor. </summary>
  32. /// <param name="listener">Chat listener implementation.</param>
  33. /// <param name="protocol">Protocol to be used by the peer.</param>
  34. public ChatPeer(IPhotonPeerListener listener, ConnectionProtocol protocol) : base(listener, protocol)
  35. {
  36. this.ConfigUnitySockets();
  37. }
  38. // Sets up the socket implementations to use, depending on platform
  39. [System.Diagnostics.Conditional("SUPPORTED_UNITY")]
  40. private void ConfigUnitySockets()
  41. {
  42. Type websocketType = null;
  43. #if (UNITY_XBOXONE || UNITY_GAMECORE) && !UNITY_EDITOR
  44. websocketType = Type.GetType("ExitGames.Client.Photon.SocketNativeSource, Assembly-CSharp", false);
  45. if (websocketType == null)
  46. {
  47. websocketType = Type.GetType("ExitGames.Client.Photon.SocketNativeSource, Assembly-CSharp-firstpass", false);
  48. }
  49. if (websocketType == null)
  50. {
  51. websocketType = Type.GetType("ExitGames.Client.Photon.SocketNativeSource, PhotonRealtime", false);
  52. }
  53. if (websocketType != null)
  54. {
  55. this.SocketImplementationConfig[ConnectionProtocol.Udp] = websocketType; // on Xbox, the native socket plugin supports UDP as well
  56. }
  57. #else
  58. // to support WebGL export in Unity, we find and assign the SocketWebTcp class (if it's in the project).
  59. // alternatively class SocketWebTcp might be in the Photon3Unity3D.dll
  60. websocketType = Type.GetType("ExitGames.Client.Photon.SocketWebTcp, PhotonWebSocket", false);
  61. if (websocketType == null)
  62. {
  63. websocketType = Type.GetType("ExitGames.Client.Photon.SocketWebTcp, Assembly-CSharp-firstpass", false);
  64. }
  65. if (websocketType == null)
  66. {
  67. websocketType = Type.GetType("ExitGames.Client.Photon.SocketWebTcp, Assembly-CSharp", false);
  68. }
  69. #endif
  70. if (websocketType != null)
  71. {
  72. this.SocketImplementationConfig[ConnectionProtocol.WebSocket] = websocketType;
  73. this.SocketImplementationConfig[ConnectionProtocol.WebSocketSecure] = websocketType;
  74. }
  75. #if NET_4_6 && (UNITY_EDITOR || !ENABLE_IL2CPP) && !NETFX_CORE
  76. this.SocketImplementationConfig[ConnectionProtocol.Udp] = typeof(SocketUdpAsync);
  77. this.SocketImplementationConfig[ConnectionProtocol.Tcp] = typeof(SocketTcpAsync);
  78. #endif
  79. }
  80. /// <summary>If not zero, this is used for the name server port on connect. Independent of protocol (so this better matches). Set by ChatClient.ConnectUsingSettings.</summary>
  81. /// <remarks>This is reset when the protocol fallback is used.</remarks>
  82. public ushort NameServerPortOverride;
  83. /// <summary>
  84. /// Gets the NameServer Address (with prefix and port), based on the set protocol (this.UsedProtocol).
  85. /// </summary>
  86. /// <returns>NameServer Address (with prefix and port).</returns>
  87. private string GetNameServerAddress()
  88. {
  89. var protocolPort = 0;
  90. ProtocolToNameServerPort.TryGetValue(this.TransportProtocol, out protocolPort);
  91. if (this.NameServerPortOverride != 0)
  92. {
  93. this.Listener.DebugReturn(DebugLevel.INFO, string.Format("Using NameServerPortInAppSettings as port for Name Server: {0}", this.NameServerPortOverride));
  94. protocolPort = this.NameServerPortOverride;
  95. }
  96. switch (this.TransportProtocol)
  97. {
  98. case ConnectionProtocol.Udp:
  99. case ConnectionProtocol.Tcp:
  100. return string.Format("{0}:{1}", NameServerHost, protocolPort);
  101. case ConnectionProtocol.WebSocket:
  102. return string.Format("ws://{0}:{1}", NameServerHost, protocolPort);
  103. case ConnectionProtocol.WebSocketSecure:
  104. return string.Format("wss://{0}:{1}", NameServerHost, protocolPort);
  105. default:
  106. throw new ArgumentOutOfRangeException();
  107. }
  108. }
  109. /// <summary> Authenticates on NameServer. </summary>
  110. /// <returns>If the authentication operation request could be sent.</returns>
  111. public bool AuthenticateOnNameServer(string appId, string appVersion, string region, AuthenticationValues authValues)
  112. {
  113. if (this.DebugOut >= DebugLevel.INFO)
  114. {
  115. this.Listener.DebugReturn(DebugLevel.INFO, "OpAuthenticate()");
  116. }
  117. var opParameters = new Dictionary<byte, object>();
  118. opParameters[ParameterCode.AppVersion] = appVersion;
  119. opParameters[ParameterCode.ApplicationId] = appId;
  120. opParameters[ParameterCode.Region] = region;
  121. if (authValues != null)
  122. {
  123. if (!string.IsNullOrEmpty(authValues.UserId))
  124. {
  125. opParameters[ParameterCode.UserId] = authValues.UserId;
  126. }
  127. if (authValues.AuthType != CustomAuthenticationType.None)
  128. {
  129. opParameters[ParameterCode.ClientAuthenticationType] = (byte) authValues.AuthType;
  130. if (authValues.Token != null)
  131. {
  132. opParameters[ParameterCode.Secret] = authValues.Token;
  133. }
  134. else
  135. {
  136. if (!string.IsNullOrEmpty(authValues.AuthGetParameters))
  137. {
  138. opParameters[ParameterCode.ClientAuthenticationParams] = authValues.AuthGetParameters;
  139. }
  140. if (authValues.AuthPostData != null)
  141. {
  142. opParameters[ParameterCode.ClientAuthenticationData] = authValues.AuthPostData;
  143. }
  144. }
  145. }
  146. }
  147. return this.SendOperation(ChatOperationCode.Authenticate, opParameters, new SendOptions() { Reliability = true, Encrypt = this.IsEncryptionAvailable });
  148. }
  149. }
  150. /// <summary>
  151. /// Options for optional "Custom Authentication" services used with Photon. Used by OpAuthenticate after connecting to Photon.
  152. /// </summary>
  153. public enum CustomAuthenticationType : byte
  154. {
  155. /// <summary>Use a custom authentication service. Currently the only implemented option.</summary>
  156. Custom = 0,
  157. /// <summary>Authenticates users by their Steam Account. Set Steam's ticket as "ticket" via AddAuthParameter().</summary>
  158. Steam = 1,
  159. /// <summary>Authenticates users by their Facebook Account. Set Facebooks's tocken as "token" via AddAuthParameter().</summary>
  160. Facebook = 2,
  161. /// <summary>Authenticates users by their Oculus Account and token. Set Oculus' userid as "userid" and nonce as "nonce" via AddAuthParameter().</summary>
  162. Oculus = 3,
  163. /// <summary>Authenticates users by their PSN Account and token on PS4. Set token as "token", env as "env" and userName as "userName" via AddAuthParameter().</summary>
  164. PlayStation4 = 4,
  165. [Obsolete("Use PlayStation4 or PlayStation5 as needed")]
  166. PlayStation = 4,
  167. /// <summary>Authenticates users by their Xbox Account. Pass the XSTS token via SetAuthPostData().</summary>
  168. Xbox = 5,
  169. /// <summary>Authenticates users by their HTC Viveport Account. Set userToken as "userToken" via AddAuthParameter().</summary>
  170. Viveport = 10,
  171. /// <summary>Authenticates users by their NSA ID. Set token as "token" and appversion as "appversion" via AddAuthParameter(). The appversion is optional.</summary>
  172. NintendoSwitch = 11,
  173. /// <summary>Authenticates users by their PSN Account and token on PS5. Set token as "token", env as "env" and userName as "userName" via AddAuthParameter().</summary>
  174. PlayStation5 = 12,
  175. [Obsolete("Use PlayStation4 or PlayStation5 as needed")]
  176. Playstation5 = 12,
  177. /// <summary>Authenticates users with Epic Online Services (EOS). Set token as "token" and ownershipToken as "ownershipToken" via AddAuthParameter(). The ownershipToken is optional.</summary>
  178. Epic = 13,
  179. /// <summary>Authenticates users with Facebook Gaming api. Set token as "token" via AddAuthParameter().</summary>
  180. FacebookGaming = 15,
  181. /// <summary>Disables custom authentication. Same as not providing any AuthenticationValues for connect (more precisely for: OpAuthenticate).</summary>
  182. None = byte.MaxValue
  183. }
  184. /// <summary>
  185. /// Container for user authentication in Photon. Set AuthValues before you connect - all else is handled.
  186. /// </summary>
  187. /// <remarks>
  188. /// On Photon, user authentication is optional but can be useful in many cases.
  189. /// If you want to FindFriends, a unique ID per user is very practical.
  190. ///
  191. /// There are basically three options for user authentication: None at all, the client sets some UserId
  192. /// or you can use some account web-service to authenticate a user (and set the UserId server-side).
  193. ///
  194. /// Custom Authentication lets you verify end-users by some kind of login or token. It sends those
  195. /// values to Photon which will verify them before granting access or disconnecting the client.
  196. ///
  197. /// The AuthValues are sent in OpAuthenticate when you connect, so they must be set before you connect.
  198. /// If the AuthValues.UserId is null or empty when it's sent to the server, then the Photon Server assigns a UserId!
  199. ///
  200. /// The Photon Cloud Dashboard will let you enable this feature and set important server values for it.
  201. /// https://dashboard.photonengine.com
  202. /// </remarks>
  203. public class AuthenticationValues
  204. {
  205. /// <summary>See AuthType.</summary>
  206. private CustomAuthenticationType authType = CustomAuthenticationType.None;
  207. /// <summary>The type of authentication provider that should be used. Defaults to None (no auth whatsoever).</summary>
  208. /// <remarks>Several auth providers are available and CustomAuthenticationType.Custom can be used if you build your own service.</remarks>
  209. public CustomAuthenticationType AuthType
  210. {
  211. get { return authType; }
  212. set { authType = value; }
  213. }
  214. /// <summary>This string must contain any (http get) parameters expected by the used authentication service. By default, username and token.</summary>
  215. /// <remarks>
  216. /// Maps to operation parameter 216.
  217. /// Standard http get parameters are used here and passed on to the service that's defined in the server (Photon Cloud Dashboard).
  218. /// </remarks>
  219. public string AuthGetParameters { get; set; }
  220. /// <summary>Data to be passed-on to the auth service via POST. Default: null (not sent). Either string or byte[] (see setters).</summary>
  221. /// <remarks>Maps to operation parameter 214.</remarks>
  222. public object AuthPostData { get; private set; }
  223. /// <summary>Internal <b>Photon token</b>. After initial authentication, Photon provides a token for this client, subsequently used as (cached) validation.</summary>
  224. /// <remarks>Any token for custom authentication should be set via SetAuthPostData or AddAuthParameter.</remarks>
  225. public object Token { get; protected internal set; }
  226. /// <summary>The UserId should be a unique identifier per user. This is for finding friends, etc..</summary>
  227. /// <remarks>See remarks of AuthValues for info about how this is set and used.</remarks>
  228. public string UserId { get; set; }
  229. /// <summary>Creates empty auth values without any info.</summary>
  230. public AuthenticationValues()
  231. {
  232. }
  233. /// <summary>Creates minimal info about the user. If this is authenticated or not, depends on the set AuthType.</summary>
  234. /// <param name="userId">Some UserId to set in Photon.</param>
  235. public AuthenticationValues(string userId)
  236. {
  237. this.UserId = userId;
  238. }
  239. /// <summary>Sets the data to be passed-on to the auth service via POST.</summary>
  240. /// <remarks>AuthPostData is just one value. Each SetAuthPostData replaces any previous value. It can be either a string, a byte[] or a dictionary.</remarks>
  241. /// <param name="stringData">String data to be used in the body of the POST request. Null or empty string will set AuthPostData to null.</param>
  242. public virtual void SetAuthPostData(string stringData)
  243. {
  244. this.AuthPostData = (string.IsNullOrEmpty(stringData)) ? null : stringData;
  245. }
  246. /// <summary>Sets the data to be passed-on to the auth service via POST.</summary>
  247. /// <remarks>AuthPostData is just one value. Each SetAuthPostData replaces any previous value. It can be either a string, a byte[] or a dictionary.</remarks>
  248. /// <param name="byteData">Binary token / auth-data to pass on.</param>
  249. public virtual void SetAuthPostData(byte[] byteData)
  250. {
  251. this.AuthPostData = byteData;
  252. }
  253. /// <summary>Sets data to be passed-on to the auth service as Json (Content-Type: "application/json") via Post.</summary>
  254. /// <remarks>AuthPostData is just one value. Each SetAuthPostData replaces any previous value. It can be either a string, a byte[] or a dictionary.</remarks>
  255. /// <param name="dictData">A authentication-data dictionary will be converted to Json and passed to the Auth webservice via HTTP Post.</param>
  256. public virtual void SetAuthPostData(Dictionary<string, object> dictData)
  257. {
  258. this.AuthPostData = dictData;
  259. }
  260. /// <summary>Adds a key-value pair to the get-parameters used for Custom Auth (AuthGetParameters).</summary>
  261. /// <remarks>This method does uri-encoding for you.</remarks>
  262. /// <param name="key">Key for the value to set.</param>
  263. /// <param name="value">Some value relevant for Custom Authentication.</param>
  264. public virtual void AddAuthParameter(string key, string value)
  265. {
  266. string ampersand = string.IsNullOrEmpty(this.AuthGetParameters) ? "" : "&";
  267. this.AuthGetParameters = string.Format("{0}{1}{2}={3}", this.AuthGetParameters, ampersand, System.Uri.EscapeDataString(key), System.Uri.EscapeDataString(value));
  268. }
  269. /// <summary>
  270. /// Transform this object into string.
  271. /// </summary>
  272. /// <returns>string representation of this object.</returns>
  273. public override string ToString()
  274. {
  275. return string.Format("AuthenticationValues Type: {3} UserId: {0}, GetParameters: {1} Token available: {2}", this.UserId, this.AuthGetParameters, this.Token != null, this.AuthType);
  276. }
  277. /// <summary>
  278. /// Make a copy of the current object.
  279. /// </summary>
  280. /// <param name="copy">The object to be copied into.</param>
  281. /// <returns>The copied object.</returns>
  282. public AuthenticationValues CopyTo(AuthenticationValues copy)
  283. {
  284. copy.AuthType = this.AuthType;
  285. copy.AuthGetParameters = this.AuthGetParameters;
  286. copy.AuthPostData = this.AuthPostData;
  287. copy.UserId = this.UserId;
  288. return copy;
  289. }
  290. }
  291. /// <summary>Class for constants. Codes for parameters of Operations and Events.</summary>
  292. public class ParameterCode
  293. {
  294. /// <summary>(224) Your application's ID: a name on your own Photon or a GUID on the Photon Cloud</summary>
  295. public const byte ApplicationId = 224;
  296. /// <summary>(221) Internally used to establish encryption</summary>
  297. public const byte Secret = 221;
  298. /// <summary>(220) Version of your application</summary>
  299. public const byte AppVersion = 220;
  300. /// <summary>(217) This key's (byte) value defines the target custom authentication type/service the client connects with. Used in OpAuthenticate</summary>
  301. public const byte ClientAuthenticationType = 217;
  302. /// <summary>(216) This key's (string) value provides parameters sent to the custom authentication type/service the client connects with. Used in OpAuthenticate</summary>
  303. public const byte ClientAuthenticationParams = 216;
  304. /// <summary>(214) This key's (string or byte[]) value provides parameters sent to the custom authentication service setup in Photon Dashboard. Used in OpAuthenticate</summary>
  305. public const byte ClientAuthenticationData = 214;
  306. /// <summary>(210) Used for region values in OpAuth and OpGetRegions.</summary>
  307. public const byte Region = 210;
  308. /// <summary>(230) Address of a (game) server to use.</summary>
  309. public const byte Address = 230;
  310. /// <summary>(225) User's ID</summary>
  311. public const byte UserId = 225;
  312. }
  313. /// <summary>
  314. /// ErrorCode defines the default codes associated with Photon client/server communication.
  315. /// </summary>
  316. public class ErrorCode
  317. {
  318. /// <summary>(0) is always "OK", anything else an error or specific situation.</summary>
  319. public const int Ok = 0;
  320. // server - Photon low(er) level: <= 0
  321. /// <summary>
  322. /// (-3) Operation can't be executed yet (e.g. OpJoin can't be called before being authenticated, RaiseEvent cant be used before getting into a room).
  323. /// </summary>
  324. /// <remarks>
  325. /// Before you call any operations on the Cloud servers, the automated client workflow must complete its authorization.
  326. /// In PUN, wait until State is: JoinedLobby or ConnectedToMaster
  327. /// </remarks>
  328. public const int OperationNotAllowedInCurrentState = -3;
  329. /// <summary>(-2) The operation you called is not implemented on the server (application) you connect to. Make sure you run the fitting applications.</summary>
  330. public const int InvalidOperationCode = -2;
  331. /// <summary>(-1) Something went wrong in the server. Try to reproduce and contact Exit Games.</summary>
  332. public const int InternalServerError = -1;
  333. // server - PhotonNetwork: 0x7FFF and down
  334. // logic-level error codes start with short.max
  335. /// <summary>(32767) Authentication failed. Possible cause: AppId is unknown to Photon (in cloud service).</summary>
  336. public const int InvalidAuthentication = 0x7FFF;
  337. /// <summary>(32766) GameId (name) already in use (can't create another). Change name.</summary>
  338. public const int GameIdAlreadyExists = 0x7FFF - 1;
  339. /// <summary>(32765) Game is full. This rarely happens when some player joined the room before your join completed.</summary>
  340. public const int GameFull = 0x7FFF - 2;
  341. /// <summary>(32764) Game is closed and can't be joined. Join another game.</summary>
  342. public const int GameClosed = 0x7FFF - 3;
  343. /// <summary>(32762) Not in use currently.</summary>
  344. public const int ServerFull = 0x7FFF - 5;
  345. /// <summary>(32761) Not in use currently.</summary>
  346. public const int UserBlocked = 0x7FFF - 6;
  347. /// <summary>(32760) Random matchmaking only succeeds if a room exists that is neither closed nor full. Repeat in a few seconds or create a new room.</summary>
  348. public const int NoRandomMatchFound = 0x7FFF - 7;
  349. /// <summary>(32758) Join can fail if the room (name) is not existing (anymore). This can happen when players leave while you join.</summary>
  350. public const int GameDoesNotExist = 0x7FFF - 9;
  351. /// <summary>(32757) Authorization on the Photon Cloud failed because the concurrent users (CCU) limit of the app's subscription is reached.</summary>
  352. /// <remarks>
  353. /// Unless you have a plan with "CCU Burst", clients might fail the authentication step during connect.
  354. /// Affected client are unable to call operations. Please note that players who end a game and return
  355. /// to the master server will disconnect and re-connect, which means that they just played and are rejected
  356. /// in the next minute / re-connect.
  357. /// This is a temporary measure. Once the CCU is below the limit, players will be able to connect an play again.
  358. ///
  359. /// OpAuthorize is part of connection workflow but only on the Photon Cloud, this error can happen.
  360. /// Self-hosted Photon servers with a CCU limited license won't let a client connect at all.
  361. /// </remarks>
  362. public const int MaxCcuReached = 0x7FFF - 10;
  363. /// <summary>(32756) Authorization on the Photon Cloud failed because the app's subscription does not allow to use a particular region's server.</summary>
  364. /// <remarks>
  365. /// Some subscription plans for the Photon Cloud are region-bound. Servers of other regions can't be used then.
  366. /// Check your master server address and compare it with your Photon Cloud Dashboard's info.
  367. /// https://cloud.photonengine.com/dashboard
  368. ///
  369. /// OpAuthorize is part of connection workflow but only on the Photon Cloud, this error can happen.
  370. /// Self-hosted Photon servers with a CCU limited license won't let a client connect at all.
  371. /// </remarks>
  372. public const int InvalidRegion = 0x7FFF - 11;
  373. /// <summary>
  374. /// (32755) Custom Authentication of the user failed due to setup reasons (see Cloud Dashboard) or the provided user data (like username or token). Check error message for details.
  375. /// </summary>
  376. public const int CustomAuthenticationFailed = 0x7FFF - 12;
  377. /// <summary>(32753) The Authentication ticket expired. Usually, this is refreshed behind the scenes. Connect (and authorize) again.</summary>
  378. public const int AuthenticationTicketExpired = 0x7FF1;
  379. }
  380. }