Seven is the number.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

259 lines
10 KiB

3 years ago
  1. // ----------------------------------------------------------------------------
  2. // <copyright file="AccountService.cs" company="Exit Games GmbH">
  3. // Photon Cloud Account Service - Copyright (C) 2012 Exit Games GmbH
  4. // </copyright>
  5. // <summary>
  6. // Provides methods to register a new user-account for the Photon Cloud and
  7. // get the resulting appId.
  8. // </summary>
  9. // <author>developer@exitgames.com</author>
  10. // ----------------------------------------------------------------------------
  11. #if UNITY_2017_4_OR_NEWER
  12. #define SUPPORTED_UNITY
  13. #endif
  14. #if UNITY_EDITOR
  15. namespace Photon.Realtime
  16. {
  17. using System;
  18. using UnityEngine;
  19. using System.Collections.Generic;
  20. using System.Text.RegularExpressions;
  21. using ExitGames.Client.Photon;
  22. /// <summary>
  23. /// Creates a instance of the Account Service to register Photon Cloud accounts.
  24. /// </summary>
  25. public class AccountService
  26. {
  27. private const string ServiceUrl = "https://partner.photonengine.com/api/{0}/User/RegisterEx";
  28. private readonly Dictionary<string, string> RequestHeaders = new Dictionary<string, string>
  29. {
  30. { "Content-Type", "application/json" },
  31. { "x-functions-key", "" }
  32. };
  33. private const string DefaultContext = "Unity";
  34. private const string DefaultToken = "VQ920wVUieLHT9c3v1ZCbytaLXpXbktUztKb3iYLCdiRKjUagcl6eg==";
  35. /// <summary>
  36. /// third parties custom context, if null, defaults to DefaultContext property value
  37. /// </summary>
  38. public string CustomContext = null;
  39. /// <summary>
  40. /// third parties custom token. If null, defaults to DefaultToken property value
  41. /// </summary>
  42. public string CustomToken = null;
  43. /// <summary>
  44. /// If this AccountService instance is currently waiting for a response. While pending, RegisterByEmail is blocked.
  45. /// </summary>
  46. public bool RequestPendingResult = false;
  47. /// <summary>
  48. /// Attempts to create a Photon Cloud Account asynchronously. Blocked while RequestPendingResult is true.
  49. /// </summary>
  50. /// <remarks>
  51. /// Once your callback is called, check ReturnCode, Message and AppId to get the result of this attempt.
  52. /// </remarks>
  53. /// <param name="email">Email of the account.</param>
  54. /// <param name="serviceTypes">Defines which type of Photon-service is being requested.</param>
  55. /// <param name="callback">Called when the result is available.</param>
  56. /// <param name="errorCallback">Called when the request failed.</param>
  57. public bool RegisterByEmail(string email, List<ServiceTypes> serviceTypes, Action<AccountServiceResponse> callback = null, Action<string> errorCallback = null)
  58. {
  59. if (this.RequestPendingResult)
  60. {
  61. Debug.LogError("Registration request pending result. Not sending another.");
  62. return false;
  63. }
  64. if (!IsValidEmail(email))
  65. {
  66. Debug.LogErrorFormat("Email \"{0}\" is not valid", email);
  67. return false;
  68. }
  69. string serviceTypeString = GetServiceTypesFromList(serviceTypes);
  70. if (string.IsNullOrEmpty(serviceTypeString))
  71. {
  72. Debug.LogError("serviceTypes string is null or empty");
  73. return false;
  74. }
  75. string fullUrl = GetUrlWithQueryStringEscaped(email, serviceTypeString);
  76. RequestHeaders["x-functions-key"] = string.IsNullOrEmpty(CustomToken) ? DefaultToken : CustomToken;
  77. this.RequestPendingResult = true;
  78. PhotonEditorUtils.StartCoroutine(
  79. PhotonEditorUtils.HttpPost(fullUrl,
  80. RequestHeaders,
  81. null,
  82. s =>
  83. {
  84. this.RequestPendingResult = false;
  85. //Debug.LogWarningFormat("received response {0}", s);
  86. if (string.IsNullOrEmpty(s))
  87. {
  88. if (errorCallback != null)
  89. {
  90. errorCallback("Server's response was empty. Please register through account website during this service interruption.");
  91. }
  92. }
  93. else
  94. {
  95. AccountServiceResponse ase = this.ParseResult(s);
  96. if (ase == null)
  97. {
  98. if (errorCallback != null)
  99. {
  100. errorCallback("Error parsing registration response. Please try registering from account website");
  101. }
  102. }
  103. else if (callback != null)
  104. {
  105. callback(ase);
  106. }
  107. }
  108. },
  109. e =>
  110. {
  111. this.RequestPendingResult = false;
  112. if (errorCallback != null)
  113. {
  114. errorCallback(e);
  115. }
  116. })
  117. );
  118. return true;
  119. }
  120. private string GetUrlWithQueryStringEscaped(string email, string serviceTypes)
  121. {
  122. string emailEscaped = UnityEngine.Networking.UnityWebRequest.EscapeURL(email);
  123. string st = UnityEngine.Networking.UnityWebRequest.EscapeURL(serviceTypes);
  124. string uv = UnityEngine.Networking.UnityWebRequest.EscapeURL(Application.unityVersion);
  125. string av = UnityEngine.Networking.UnityWebRequest.EscapeURL(new PhotonPeer(ConnectionProtocol.Udp).ClientVersion);
  126. string serviceUrl = string.Format(ServiceUrl, string.IsNullOrEmpty(CustomContext) ? DefaultContext : CustomContext );
  127. return string.Format("{0}?email={1}&st={2}&uv={3}&av={4}", serviceUrl, emailEscaped, st, uv, av);
  128. }
  129. /// <summary>
  130. /// Reads the Json response and applies it to local properties.
  131. /// </summary>
  132. /// <param name="result"></param>
  133. private AccountServiceResponse ParseResult(string result)
  134. {
  135. try
  136. {
  137. AccountServiceResponse res = JsonUtility.FromJson<AccountServiceResponse>(result);
  138. // Unity's JsonUtility does not support deserializing Dictionary, we manually parse it, dirty & ugly af, better then using a 3rd party lib
  139. if (res.ReturnCode == AccountServiceReturnCodes.Success)
  140. {
  141. string[] parts = result.Split(new[] { "\"ApplicationIds\":{" }, StringSplitOptions.RemoveEmptyEntries);
  142. parts = parts[1].Split('}');
  143. string applicationIds = parts[0];
  144. if (!string.IsNullOrEmpty(applicationIds))
  145. {
  146. parts = applicationIds.Split(new[] { ',', '"', ':' }, StringSplitOptions.RemoveEmptyEntries);
  147. res.ApplicationIds = new Dictionary<string, string>(parts.Length / 2);
  148. for (int i = 0; i < parts.Length; i = i + 2)
  149. {
  150. res.ApplicationIds.Add(parts[i], parts[i + 1]);
  151. }
  152. }
  153. else
  154. {
  155. Debug.LogError("The server did not return any AppId, ApplicationIds was empty in the response.");
  156. return null;
  157. }
  158. }
  159. return res;
  160. }
  161. catch (Exception ex) // probably JSON parsing exception, check if returned string is valid JSON
  162. {
  163. Debug.LogException(ex);
  164. return null;
  165. }
  166. }
  167. /// <summary>
  168. /// Turns the list items to a comma separated string. Returns null if list is null or empty.
  169. /// </summary>
  170. /// <param name="appTypes">List of service types.</param>
  171. /// <returns>Returns null if list is null or empty.</returns>
  172. private static string GetServiceTypesFromList(List<ServiceTypes> appTypes)
  173. {
  174. if (appTypes == null || appTypes.Count <= 0)
  175. {
  176. return null;
  177. }
  178. string serviceTypes = ((int)appTypes[0]).ToString();
  179. for (int i = 1; i < appTypes.Count; i++)
  180. {
  181. int appType = (int)appTypes[i];
  182. serviceTypes = string.Format("{0},{1}", serviceTypes, appType);
  183. }
  184. return serviceTypes;
  185. }
  186. // RFC2822 compliant matching 99.9% of all email addresses in actual use today
  187. // according to http://www.regular-expressions.info/email.html [22.02.2012]
  188. private static Regex reg = new Regex("^((?>[a-zA-Z\\d!#$%&'*+\\-/=?^_{|}~]+\\x20*|\"((?=[\\x01-\\x7f])[^\"\\]|\\[\\x01-\\x7f])*\"\\x20*)*(?<angle><))?((?!\\.)(?>\\.?[a-zA-Z\\d!#$%&'*+\\-/=?^_{|}~]+)+|\"((?=[\\x01-\\x7f])[^\"\\]|\\[\\x01-\\x7f])*\")@(((?!-)[a-zA-Z\\d\\-]+(?<!-)\\.)+[a-zA-Z]{2,}|\\[(((?(?<!\\[)\\.)(25[0-5]|2[0-4]\\d|[01]?\\d?\\d)){4}|[a-zA-Z\\d\\-]*[a-zA-Z\\d]:((?=[\\x01-\\x7f])[^\\\\[\\]]|\\[\\x01-\\x7f])+)\\])(?(angle)>)$",
  189. RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
  190. public static bool IsValidEmail(string mailAddress)
  191. {
  192. if (string.IsNullOrEmpty(mailAddress))
  193. {
  194. return false;
  195. }
  196. var result = reg.Match(mailAddress);
  197. return result.Success;
  198. }
  199. }
  200. [Serializable]
  201. public class AccountServiceResponse
  202. {
  203. public int ReturnCode;
  204. public string Message;
  205. public Dictionary<string, string> ApplicationIds; // Unity's JsonUtility does not support deserializing Dictionary
  206. }
  207. public class AccountServiceReturnCodes
  208. {
  209. public static int Success = 0;
  210. public static int EmailAlreadyRegistered = 8;
  211. public static int InvalidParameters = 12;
  212. }
  213. public enum ServiceTypes
  214. {
  215. Realtime = 0,
  216. Turnbased = 1,
  217. Chat = 2,
  218. Voice = 3,
  219. TrueSync = 4,
  220. Pun = 5,
  221. Thunder = 6,
  222. Quantum = 7,
  223. Fusion = 8,
  224. Bolt = 20
  225. }
  226. }
  227. #endif