using System; using System.Collections; using System.Collections.Generic; using System.Text; using PlayFab.Public; using PlayFab.SharedModels; using UnityEngine; namespace PlayFab.Internal { /// /// This is a wrapper for Http So we can better separate the functionaity of Http Requests delegated to WWW or HttpWebRequest /// public class PlayFabHttp : SingletonMonoBehaviour { private static List _apiCallQueue = new List(); // Starts initialized, and is nulled when it's flushed public delegate void ApiProcessingEvent(TEventArgs e); public delegate void ApiProcessErrorEvent(PlayFabRequestCommon request, PlayFabError error); public static event ApiProcessingEvent ApiProcessingEventHandler; public static event ApiProcessErrorEvent ApiProcessingErrorEventHandler; public static readonly Dictionary GlobalHeaderInjection = new Dictionary(); private static IPlayFabLogger _logger; #if !DISABLE_PLAYFABENTITY_API && !DISABLE_PLAYFABCLIENT_API private static IScreenTimeTracker screenTimeTracker = new ScreenTimeTracker(); private const float delayBetweenBatches = 5.0f; #endif #if PLAYFAB_REQUEST_TIMING public struct RequestTiming { public DateTime StartTimeUtc; public string ApiEndpoint; public int WorkerRequestMs; public int MainThreadRequestMs; } public delegate void ApiRequestTimingEvent(RequestTiming time); public static event ApiRequestTimingEvent ApiRequestTimingEventHandler; #endif /// /// Return the number of api calls that are waiting for results from the server /// /// public static int GetPendingMessages() { var transport = PluginManager.GetPlugin(PluginContract.PlayFab_Transport); return transport.IsInitialized ? transport.GetPendingMessages() : 0; } /// /// This initializes the GameObject and ensures it is in the scene. /// public static void InitializeHttp() { if (string.IsNullOrEmpty(PlayFabSettings.TitleId)) throw new PlayFabException(PlayFabExceptionCode.TitleNotSet, "You must set PlayFabSettings.TitleId before making API Calls."); var transport = PluginManager.GetPlugin(PluginContract.PlayFab_Transport); if (transport.IsInitialized) return; transport.Initialize(); CreateInstance(); // Invoke the SingletonMonoBehaviour } /// /// This initializes the GameObject and ensures it is in the scene. /// public static void InitializeLogger(IPlayFabLogger setLogger = null) { if (_logger != null) throw new InvalidOperationException("Once initialized, the logger cannot be reset."); if (setLogger == null) setLogger = new PlayFabLogger(); _logger = setLogger; } #if !DISABLE_PLAYFABENTITY_API && !DISABLE_PLAYFABCLIENT_API /// /// This initializes ScreenTimeTracker object and notifying it to start sending info. /// /// Result of the user's login, represent user ID public static void InitializeScreenTimeTracker(string entityId, string entityType, string playFabUserId) { screenTimeTracker.ClientSessionStart(entityId, entityType, playFabUserId); instance.StartCoroutine(SendScreenTimeEvents(delayBetweenBatches)); } /// /// This function will send Screen Time events on a periodic basis. /// /// Delay between batches, in seconds private static IEnumerator SendScreenTimeEvents(float secondsBetweenBatches) { WaitForSeconds delay = new WaitForSeconds(secondsBetweenBatches); while (!PlayFabSettings.DisableFocusTimeCollection) { screenTimeTracker.Send(); yield return delay; } } #endif public static void SimpleGetCall(string fullUrl, Action successCallback, Action errorCallback) { InitializeHttp(); PluginManager.GetPlugin(PluginContract.PlayFab_Transport).SimpleGetCall(fullUrl, successCallback, errorCallback); } public static void SimplePutCall(string fullUrl, byte[] payload, Action successCallback, Action errorCallback) { InitializeHttp(); PluginManager.GetPlugin(PluginContract.PlayFab_Transport).SimplePutCall(fullUrl, payload, successCallback, errorCallback); } public static void SimplePostCall(string fullUrl, byte[] payload, Action successCallback, Action errorCallback) { InitializeHttp(); PluginManager.GetPlugin(PluginContract.PlayFab_Transport).SimplePostCall(fullUrl, payload, successCallback, errorCallback); } protected internal static void MakeApiCall(string apiEndpoint, PlayFabRequestCommon request, AuthType authType, Action resultCallback, Action errorCallback, object customData = null, Dictionary extraHeaders = null, PlayFabAuthenticationContext authenticationContext = null, PlayFabApiSettings apiSettings = null, IPlayFabInstanceApi instanceApi = null) where TResult : PlayFabResultCommon { apiSettings = apiSettings ?? PlayFabSettings.staticSettings; var fullUrl = apiSettings.GetFullUrl(apiEndpoint, apiSettings.RequestGetParams); _MakeApiCall(apiEndpoint, fullUrl, request, authType, resultCallback, errorCallback, customData, extraHeaders, false, authenticationContext, apiSettings, instanceApi); } protected internal static void MakeApiCallWithFullUri(string fullUri, PlayFabRequestCommon request, AuthType authType, Action resultCallback, Action errorCallback, object customData = null, Dictionary extraHeaders = null, PlayFabAuthenticationContext authenticationContext = null, PlayFabApiSettings apiSettings = null, IPlayFabInstanceApi instanceApi = null) where TResult : PlayFabResultCommon { apiSettings = apiSettings ?? PlayFabSettings.staticSettings; // This will not be called if environment file does not exist or does not contain property the debugging URI _MakeApiCall(null, fullUri, request, authType, resultCallback, errorCallback, customData, extraHeaders, false, authenticationContext, apiSettings, instanceApi); } /// /// Internal method for Make API Calls /// private static void _MakeApiCall(string apiEndpoint, string fullUrl, PlayFabRequestCommon request, AuthType authType, Action resultCallback, Action errorCallback, object customData, Dictionary extraHeaders, bool allowQueueing, PlayFabAuthenticationContext authenticationContext, PlayFabApiSettings apiSettings, IPlayFabInstanceApi instanceApi) where TResult : PlayFabResultCommon { InitializeHttp(); SendEvent(apiEndpoint, request, null, ApiProcessingEventType.Pre); var serializer = PluginManager.GetPlugin(PluginContract.PlayFab_Serializer); var reqContainer = new CallRequestContainer { ApiEndpoint = apiEndpoint, FullUrl = fullUrl, settings = apiSettings, context = authenticationContext, CustomData = customData, Payload = Encoding.UTF8.GetBytes(serializer.SerializeObject(request)), ApiRequest = request, ErrorCallback = errorCallback, RequestHeaders = extraHeaders ?? new Dictionary(), // Use any headers provided by the customer instanceApi = instanceApi }; // Append any additional headers foreach (var pair in GlobalHeaderInjection) if (!reqContainer.RequestHeaders.ContainsKey(pair.Key)) reqContainer.RequestHeaders[pair.Key] = pair.Value; #if PLAYFAB_REQUEST_TIMING reqContainer.Timing.StartTimeUtc = DateTime.UtcNow; reqContainer.Timing.ApiEndpoint = apiEndpoint; #endif // Add PlayFab Headers var transport = PluginManager.GetPlugin(PluginContract.PlayFab_Transport); reqContainer.RequestHeaders["X-ReportErrorAsSuccess"] = "true"; // Makes processing PlayFab errors a little easier reqContainer.RequestHeaders["X-PlayFabSDK"] = PlayFabSettings.VersionString; // Tell PlayFab which SDK this is switch (authType) { #if ENABLE_PLAYFABSERVER_API || ENABLE_PLAYFABADMIN_API || UNITY_EDITOR || ENABLE_PLAYFAB_SECRETKEY case AuthType.DevSecretKey: if (apiSettings.DeveloperSecretKey == null) throw new PlayFabException(PlayFabExceptionCode.DeveloperKeyNotSet, "DeveloperSecretKey is not found in Request, Server Instance or PlayFabSettings"); reqContainer.RequestHeaders["X-SecretKey"] = apiSettings.DeveloperSecretKey; break; #endif #if !DISABLE_PLAYFABCLIENT_API case AuthType.LoginSession: if (authenticationContext != null) reqContainer.RequestHeaders["X-Authorization"] = authenticationContext.ClientSessionTicket; break; #endif #if !DISABLE_PLAYFABENTITY_API case AuthType.EntityToken: if (authenticationContext != null) reqContainer.RequestHeaders["X-EntityToken"] = authenticationContext.EntityToken; break; #endif case AuthType.TelemetryKey: if (authenticationContext != null) reqContainer.RequestHeaders["X-TelemetryKey"] = authenticationContext.TelemetryKey; break; } // These closures preserve the TResult generic information in a way that's safe for all the devices reqContainer.DeserializeResultJson = () => { reqContainer.ApiResult = serializer.DeserializeObject(reqContainer.JsonResponse); }; reqContainer.InvokeSuccessCallback = () => { if (resultCallback != null) { resultCallback((TResult)reqContainer.ApiResult); } }; if (allowQueueing && _apiCallQueue != null) { for (var i = _apiCallQueue.Count - 1; i >= 0; i--) if (_apiCallQueue[i].ApiEndpoint == apiEndpoint) _apiCallQueue.RemoveAt(i); _apiCallQueue.Add(reqContainer); } else { transport.MakeApiCall(reqContainer); } } /// /// Internal code shared by IPlayFabHTTP implementations /// internal void OnPlayFabApiResult(CallRequestContainer reqContainer) { var result = reqContainer.ApiResult; #if !DISABLE_PLAYFABENTITY_API var entRes = result as AuthenticationModels.GetEntityTokenResponse; if (entRes != null) { PlayFabSettings.staticPlayer.EntityToken = entRes.EntityToken; } #endif #if !DISABLE_PLAYFABCLIENT_API var logRes = result as ClientModels.LoginResult; var regRes = result as ClientModels.RegisterPlayFabUserResult; if (logRes != null) { logRes.AuthenticationContext = new PlayFabAuthenticationContext(logRes.SessionTicket, logRes.EntityToken.EntityToken, logRes.PlayFabId, logRes.EntityToken.Entity.Id, logRes.EntityToken.Entity.Type); if (reqContainer.context != null) reqContainer.context.CopyFrom(logRes.AuthenticationContext); } else if (regRes != null) { regRes.AuthenticationContext = new PlayFabAuthenticationContext(regRes.SessionTicket, regRes.EntityToken.EntityToken, regRes.PlayFabId, regRes.EntityToken.Entity.Id, regRes.EntityToken.Entity.Type); if (reqContainer.context != null) reqContainer.context.CopyFrom(regRes.AuthenticationContext); } #endif } /// /// MonoBehaviour OnEnable Method /// private void OnEnable() { if (_logger != null) { _logger.OnEnable(); } #if !DISABLE_PLAYFABENTITY_API && !DISABLE_PLAYFABCLIENT_API if ((screenTimeTracker != null) && !PlayFabSettings.DisableFocusTimeCollection) { screenTimeTracker.OnEnable(); } #endif } /// /// MonoBehaviour OnDisable /// private void OnDisable() { if (_logger != null) { _logger.OnDisable(); } #if !DISABLE_PLAYFABENTITY_API && !DISABLE_PLAYFABCLIENT_API if ((screenTimeTracker != null) && !PlayFabSettings.DisableFocusTimeCollection) { screenTimeTracker.OnDisable(); } #endif } /// /// MonoBehaviour OnDestroy /// private void OnDestroy() { var transport = PluginManager.GetPlugin(PluginContract.PlayFab_Transport); if (transport.IsInitialized) { transport.OnDestroy(); } if (_logger != null) { _logger.OnDestroy(); } #if !DISABLE_PLAYFABENTITY_API && !DISABLE_PLAYFABCLIENT_API if ((screenTimeTracker != null) && !PlayFabSettings.DisableFocusTimeCollection) { screenTimeTracker.OnDestroy(); } #endif } /// /// MonoBehaviour OnApplicationFocus /// public void OnApplicationFocus(bool isFocused) { #if !DISABLE_PLAYFABENTITY_API && !DISABLE_PLAYFABCLIENT_API if ((screenTimeTracker != null) && !PlayFabSettings.DisableFocusTimeCollection) { screenTimeTracker.OnApplicationFocus(isFocused); } #endif } /// /// MonoBehaviour OnApplicationQuit /// public void OnApplicationQuit() { #if !DISABLE_PLAYFABENTITY_API && !DISABLE_PLAYFABCLIENT_API if ((screenTimeTracker != null) && !PlayFabSettings.DisableFocusTimeCollection) { screenTimeTracker.OnApplicationQuit(); } #endif } /// /// MonoBehaviour Update /// private void Update() { var transport = PluginManager.GetPlugin(PluginContract.PlayFab_Transport); if (transport.IsInitialized) { if (_apiCallQueue != null) { foreach (var eachRequest in _apiCallQueue) transport.MakeApiCall(eachRequest); // Flush the queue _apiCallQueue = null; // null this after it's flushed } transport.Update(); } while (_injectedCoroutines.Count > 0) StartCoroutine(_injectedCoroutines.Dequeue()); while (_injectedAction.Count > 0) { var action = _injectedAction.Dequeue(); if (action != null) { action.Invoke(); } } } #region Helpers protected internal static PlayFabError GeneratePlayFabError(string apiEndpoint, string json, object customData) { Dictionary errorDict = null; Dictionary> errorDetails = null; var serializer = PluginManager.GetPlugin(PluginContract.PlayFab_Serializer); try { // Deserialize the error errorDict = serializer.DeserializeObject>(json); } catch (Exception) { /* Unusual, but shouldn't actually matter */ } try { object errorDetailsString; if (errorDict != null && errorDict.TryGetValue("errorDetails", out errorDetailsString)) errorDetails = serializer.DeserializeObject>>(errorDetailsString.ToString()); } catch (Exception) { /* Unusual, but shouldn't actually matter */ } return new PlayFabError { ApiEndpoint = apiEndpoint, HttpCode = errorDict != null && errorDict.ContainsKey("code") ? Convert.ToInt32(errorDict["code"]) : 400, HttpStatus = errorDict != null && errorDict.ContainsKey("status") ? (string)errorDict["status"] : "BadRequest", Error = errorDict != null && errorDict.ContainsKey("errorCode") ? (PlayFabErrorCode)Convert.ToInt32(errorDict["errorCode"]) : PlayFabErrorCode.ServiceUnavailable, ErrorMessage = errorDict != null && errorDict.ContainsKey("errorMessage") ? (string)errorDict["errorMessage"] : json, ErrorDetails = errorDetails, CustomData = customData, RetryAfterSeconds = errorDict != null && errorDict.ContainsKey("retryAfterSeconds") ? Convert.ToUInt32(errorDict["retryAfterSeconds"]) : (uint?)null, }; } protected internal static void SendErrorEvent(PlayFabRequestCommon request, PlayFabError error) { if (ApiProcessingErrorEventHandler == null) return; try { ApiProcessingErrorEventHandler(request, error); } catch (Exception e) { Debug.LogException(e); } } protected internal static void SendEvent(string apiEndpoint, PlayFabRequestCommon request, PlayFabResultCommon result, ApiProcessingEventType eventType) { if (ApiProcessingEventHandler == null) return; try { ApiProcessingEventHandler(new ApiProcessingEventArgs { ApiEndpoint = apiEndpoint, EventType = eventType, Request = request, Result = result }); } catch (Exception e) { Debug.LogException(e); } } public static void ClearAllEvents() { ApiProcessingEventHandler = null; ApiProcessingErrorEventHandler = null; } #if PLAYFAB_REQUEST_TIMING protected internal static void SendRequestTiming(RequestTiming rt) { if (ApiRequestTimingEventHandler != null) { ApiRequestTimingEventHandler(rt); } } #endif #endregion private readonly Queue _injectedCoroutines = new Queue(); private readonly Queue _injectedAction = new Queue(); public void InjectInUnityThread(IEnumerator x) { _injectedCoroutines.Enqueue(x); } public void InjectInUnityThread(Action action) { _injectedAction.Enqueue(action); } } #region Event Classes public enum ApiProcessingEventType { Pre, Post } public class ApiProcessingEventArgs { public string ApiEndpoint; public ApiProcessingEventType EventType; public PlayFabRequestCommon Request; public PlayFabResultCommon Result; public TRequest GetRequest() where TRequest : PlayFabRequestCommon { return Request as TRequest; } } #endregion }