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.

260 lines
10 KiB

// ----------------------------------------------------------------------------
// <copyright file="AccountService.cs" company="Exit Games GmbH">
// Photon Cloud Account Service - Copyright (C) 2012 Exit Games GmbH
// </copyright>
// <summary>
// Provides methods to register a new user-account for the Photon Cloud and
// get the resulting appId.
// </summary>
// <author>developer@exitgames.com</author>
// ----------------------------------------------------------------------------
#if UNITY_2017_4_OR_NEWER
#define SUPPORTED_UNITY
#endif
#if UNITY_EDITOR
namespace Photon.Realtime
{
using System;
using UnityEngine;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using ExitGames.Client.Photon;
/// <summary>
/// Creates a instance of the Account Service to register Photon Cloud accounts.
/// </summary>
public class AccountService
{
private const string ServiceUrl = "https://partner.photonengine.com/api/{0}/User/RegisterEx";
private readonly Dictionary<string, string> RequestHeaders = new Dictionary<string, string>
{
{ "Content-Type", "application/json" },
{ "x-functions-key", "" }
};
private const string DefaultContext = "Unity";
private const string DefaultToken = "VQ920wVUieLHT9c3v1ZCbytaLXpXbktUztKb3iYLCdiRKjUagcl6eg==";
/// <summary>
/// third parties custom context, if null, defaults to DefaultContext property value
/// </summary>
public string CustomContext = null; // "PartnerCode" on the server
/// <summary>
/// third parties custom token. If null, defaults to DefaultToken property value
/// </summary>
public string CustomToken = null;
/// <summary>
/// If this AccountService instance is currently waiting for a response. While pending, RegisterByEmail is blocked.
/// </summary>
public bool RequestPendingResult = false;
/// <summary>
/// Attempts to create a Photon Cloud Account asynchronously. Blocked while RequestPendingResult is true.
/// </summary>
/// <remarks>
/// Once your callback is called, check ReturnCode, Message and AppId to get the result of this attempt.
/// </remarks>
/// <param name="email">Email of the account.</param>
/// <param name="serviceTypes">Defines which type of Photon-service is being requested.</param>
/// <param name="callback">Called when the result is available.</param>
/// <param name="errorCallback">Called when the request failed.</param>
/// <param name="origin">Can be used to identify the origin of the registration (which package is being used).</param>
public bool RegisterByEmail(string email, List<ServiceTypes> serviceTypes, Action<AccountServiceResponse> callback = null, Action<string> errorCallback = null, string origin = null)
{
if (this.RequestPendingResult)
{
Debug.LogError("Registration request pending result. Not sending another.");
return false;
}
if (!IsValidEmail(email))
{
Debug.LogErrorFormat("Email \"{0}\" is not valid", email);
return false;
}
string serviceTypeString = GetServiceTypesFromList(serviceTypes);
if (string.IsNullOrEmpty(serviceTypeString))
{
Debug.LogError("serviceTypes string is null or empty");
return false;
}
string fullUrl = GetUrlWithQueryStringEscaped(email, serviceTypeString, origin);
RequestHeaders["x-functions-key"] = string.IsNullOrEmpty(CustomToken) ? DefaultToken : CustomToken;
this.RequestPendingResult = true;
PhotonEditorUtils.StartCoroutine(
PhotonEditorUtils.HttpPost(fullUrl,
RequestHeaders,
null,
s =>
{
this.RequestPendingResult = false;
//Debug.LogWarningFormat("received response {0}", s);
if (string.IsNullOrEmpty(s))
{
if (errorCallback != null)
{
errorCallback("Server's response was empty. Please register through account website during this service interruption.");
}
}
else
{
AccountServiceResponse ase = this.ParseResult(s);
if (ase == null)
{
if (errorCallback != null)
{
errorCallback("Error parsing registration response. Please try registering from account website");
}
}
else if (callback != null)
{
callback(ase);
}
}
},
e =>
{
this.RequestPendingResult = false;
if (errorCallback != null)
{
errorCallback(e);
}
})
);
return true;
}
private string GetUrlWithQueryStringEscaped(string email, string serviceTypes, string originAv)
{
string emailEscaped = UnityEngine.Networking.UnityWebRequest.EscapeURL(email);
string st = UnityEngine.Networking.UnityWebRequest.EscapeURL(serviceTypes);
string uv = UnityEngine.Networking.UnityWebRequest.EscapeURL(Application.unityVersion);
string serviceUrl = string.Format(ServiceUrl, string.IsNullOrEmpty(CustomContext) ? DefaultContext : CustomContext );
return string.Format("{0}?email={1}&st={2}&uv={3}&av={4}", serviceUrl, emailEscaped, st, uv, originAv);
}
/// <summary>
/// Reads the Json response and applies it to local properties.
/// </summary>
/// <param name="result"></param>
private AccountServiceResponse ParseResult(string result)
{
try
{
AccountServiceResponse res = JsonUtility.FromJson<AccountServiceResponse>(result);
// Unity's JsonUtility does not support deserializing Dictionary, we manually parse it, dirty & ugly af, better then using a 3rd party lib
if (res.ReturnCode == AccountServiceReturnCodes.Success)
{
string[] parts = result.Split(new[] { "\"ApplicationIds\":{" }, StringSplitOptions.RemoveEmptyEntries);
parts = parts[1].Split('}');
string applicationIds = parts[0];
if (!string.IsNullOrEmpty(applicationIds))
{
parts = applicationIds.Split(new[] { ',', '"', ':' }, StringSplitOptions.RemoveEmptyEntries);
res.ApplicationIds = new Dictionary<string, string>(parts.Length / 2);
for (int i = 0; i < parts.Length; i = i + 2)
{
res.ApplicationIds.Add(parts[i], parts[i + 1]);
}
}
else
{
Debug.LogError("The server did not return any AppId, ApplicationIds was empty in the response.");
return null;
}
}
return res;
}
catch (Exception ex) // probably JSON parsing exception, check if returned string is valid JSON
{
Debug.LogException(ex);
return null;
}
}
/// <summary>
/// Turns the list items to a comma separated string. Returns null if list is null or empty.
/// </summary>
/// <param name="appTypes">List of service types.</param>
/// <returns>Returns null if list is null or empty.</returns>
private static string GetServiceTypesFromList(List<ServiceTypes> appTypes)
{
if (appTypes == null || appTypes.Count <= 0)
{
return null;
}
string serviceTypes = ((int)appTypes[0]).ToString();
for (int i = 1; i < appTypes.Count; i++)
{
int appType = (int)appTypes[i];
serviceTypes = string.Format("{0},{1}", serviceTypes, appType);
}
return serviceTypes;
}
// RFC2822 compliant matching 99.9% of all email addresses in actual use today
// according to http://www.regular-expressions.info/email.html [22.02.2012]
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)>)$",
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
public static bool IsValidEmail(string mailAddress)
{
if (string.IsNullOrEmpty(mailAddress))
{
return false;
}
var result = reg.Match(mailAddress);
return result.Success;
}
}
[Serializable]
public class AccountServiceResponse
{
public int ReturnCode;
public string Message;
public Dictionary<string, string> ApplicationIds; // Unity's JsonUtility does not support deserializing Dictionary
}
public class AccountServiceReturnCodes
{
public static int Success = 0;
public static int EmailAlreadyRegistered = 8;
public static int InvalidParameters = 12;
}
public enum ServiceTypes
{
Realtime = 0,
Turnbased = 1,
Chat = 2,
Voice = 3,
TrueSync = 4,
Pun = 5,
Thunder = 6,
Quantum = 7,
Fusion = 8,
Bolt = 20
}
}
#endif