AccountService.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  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; // "PartnerCode" on the server
  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. /// <param name="origin">Can be used to identify the origin of the registration (which package is being used).</param>
  58. public bool RegisterByEmail(string email, List<ServiceTypes> serviceTypes, Action<AccountServiceResponse> callback = null, Action<string> errorCallback = null, string origin = null)
  59. {
  60. if (this.RequestPendingResult)
  61. {
  62. Debug.LogError("Registration request pending result. Not sending another.");
  63. return false;
  64. }
  65. if (!IsValidEmail(email))
  66. {
  67. Debug.LogErrorFormat("Email \"{0}\" is not valid", email);
  68. return false;
  69. }
  70. string serviceTypeString = GetServiceTypesFromList(serviceTypes);
  71. if (string.IsNullOrEmpty(serviceTypeString))
  72. {
  73. Debug.LogError("serviceTypes string is null or empty");
  74. return false;
  75. }
  76. string fullUrl = GetUrlWithQueryStringEscaped(email, serviceTypeString, origin);
  77. RequestHeaders["x-functions-key"] = string.IsNullOrEmpty(CustomToken) ? DefaultToken : CustomToken;
  78. this.RequestPendingResult = true;
  79. PhotonEditorUtils.StartCoroutine(
  80. PhotonEditorUtils.HttpPost(fullUrl,
  81. RequestHeaders,
  82. null,
  83. s =>
  84. {
  85. this.RequestPendingResult = false;
  86. //Debug.LogWarningFormat("received response {0}", s);
  87. if (string.IsNullOrEmpty(s))
  88. {
  89. if (errorCallback != null)
  90. {
  91. errorCallback("Server's response was empty. Please register through account website during this service interruption.");
  92. }
  93. }
  94. else
  95. {
  96. AccountServiceResponse ase = this.ParseResult(s);
  97. if (ase == null)
  98. {
  99. if (errorCallback != null)
  100. {
  101. errorCallback("Error parsing registration response. Please try registering from account website");
  102. }
  103. }
  104. else if (callback != null)
  105. {
  106. callback(ase);
  107. }
  108. }
  109. },
  110. e =>
  111. {
  112. this.RequestPendingResult = false;
  113. if (errorCallback != null)
  114. {
  115. errorCallback(e);
  116. }
  117. })
  118. );
  119. return true;
  120. }
  121. private string GetUrlWithQueryStringEscaped(string email, string serviceTypes, string originAv)
  122. {
  123. string emailEscaped = UnityEngine.Networking.UnityWebRequest.EscapeURL(email);
  124. string st = UnityEngine.Networking.UnityWebRequest.EscapeURL(serviceTypes);
  125. string uv = UnityEngine.Networking.UnityWebRequest.EscapeURL(Application.unityVersion);
  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, originAv);
  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