787 lines
29 KiB
C#
787 lines
29 KiB
C#
using AmplitudeMiniJSON;
|
|
using BestHTTP;
|
|
using BestHTTP.Forms;
|
|
using BestHTTP.Logger;
|
|
using Discord;
|
|
using ExitGames.Client.Photon.LoadBalancing;
|
|
using RecRoom.Async;
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using UnityEditor.Build.Content;
|
|
using UnityEditor.VersionControl;
|
|
using UnityEngine;
|
|
using UnityEngine.Diagnostics;
|
|
using static UnityEngine.Networking.SyncList<T>;
|
|
|
|
namespace RecNet
|
|
{
|
|
public class Core
|
|
{
|
|
public delegate void ApiCallback(string error);
|
|
|
|
public delegate void ApiCallback<T>(string error, T result);
|
|
|
|
[Serializable]
|
|
private class NameServerResponse
|
|
{
|
|
// This too :P
|
|
public string RecNetStatus;
|
|
|
|
public string Auth;
|
|
|
|
public string API;
|
|
|
|
public string WWW;
|
|
|
|
public string Notifications;
|
|
|
|
public string Images;
|
|
|
|
// new adds...
|
|
public string CDN;
|
|
|
|
public string Commerce;
|
|
|
|
public string Matchmaking;
|
|
|
|
public string Storage;
|
|
|
|
public string Chat;
|
|
|
|
public string Leaderboard;
|
|
|
|
public string Accounts;
|
|
|
|
public string Rooms;
|
|
}
|
|
|
|
private class VersionCheckResponse : IRecNetObject
|
|
{
|
|
public bool ValidVersion { get; private set; }
|
|
|
|
public void Deserialize(Dictionary<string, object> dict)
|
|
{
|
|
ValidVersion = Util.GetKey<bool>("ValidVersion", dict);
|
|
}
|
|
}
|
|
|
|
private delegate HTTPRequest ConstructRequestCallback(Uri uri);
|
|
|
|
private delegate IEnumerator AsyncConstructRequestCallback(Uri uri, Action<HTTPRequest> callback);
|
|
|
|
private class QueuedApiCall
|
|
{
|
|
public Service Service;
|
|
public string Uri;
|
|
public ConstructRequestCallback ConstructRequest;
|
|
public AsyncConstructRequestCallback AsyncConstructRequest;
|
|
public Promise<HTTPResponse> Promise;
|
|
|
|
public QueuedApiCall(Service service, string uri, ConstructRequestCallback constructRequest)
|
|
{
|
|
Service = service;
|
|
Uri = uri;
|
|
ConstructRequest = constructRequest;
|
|
AsyncConstructRequest = null;
|
|
Promise = new Promise<HTTPResponse>();
|
|
}
|
|
|
|
public QueuedApiCall(Service service, string uri, AsyncConstructRequestCallback asyncConstructRequest)
|
|
{
|
|
Service = service;
|
|
Uri = uri;
|
|
ConstructRequest = null;
|
|
AsyncConstructRequest = asyncConstructRequest;
|
|
Promise = new Promise<HTTPResponse>();
|
|
}
|
|
}
|
|
|
|
private const int API_RETRIES = 3;
|
|
|
|
private const int PARALLEL_WORKER_THREAD_COUNT = 4;
|
|
|
|
private const int MAX_BACKOFF_EXPONENT = 6;
|
|
|
|
private const float REFRESH_LOGIN_INTERVAL = 3600f;
|
|
|
|
public static readonly Uri RecNetOfflineUri;
|
|
|
|
public const bool REC_NET_OFFLINE_MODE = false;
|
|
|
|
public static readonly string RecNetEnv;
|
|
|
|
private static bool inScreenMode;
|
|
|
|
private static bool initialized;
|
|
|
|
private static Queue<QueuedApiCall> loginQueue;
|
|
|
|
private static Queue<QueuedApiCall> serialQueue;
|
|
|
|
private static Queue<QueuedApiCall> parallelQueue;
|
|
|
|
private static Queue<QueuedApiCall> imageQueue;
|
|
|
|
private static Queue<Action> eventQueue;
|
|
|
|
public static long LocalProfileId { get; private set; }
|
|
|
|
private static Dictionary<Service, Uri> ServiceUris;
|
|
|
|
private static HashSet<string> HostsRequiringRecNetAccessToken;
|
|
|
|
public static bool IsProdOrStagingEnv => string.IsNullOrWhiteSpace(RecNetEnv) || RecNetEnv.Equals("Prod", StringComparison.OrdinalIgnoreCase) || RecNetEnv.Equals("Staging", StringComparison.OrdinalIgnoreCase);
|
|
|
|
public static bool IsDevBuildOrDevEnv => !IsProdOrStagingEnv;
|
|
|
|
public static bool InScreenMode
|
|
{
|
|
get
|
|
{
|
|
return inScreenMode;
|
|
}
|
|
set
|
|
{
|
|
if (inScreenMode != value)
|
|
{
|
|
inScreenMode = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
static Core()
|
|
{
|
|
RecNetOfflineUri = new Uri("http://offline");
|
|
inScreenMode = true;
|
|
initialized = false;
|
|
|
|
ServiceUris = new Dictionary<Service, Uri>();
|
|
HostsRequiringRecNetAccessToken = new HashSet<string>();
|
|
|
|
loginQueue = new Queue<QueuedApiCall>();
|
|
serialQueue = new Queue<QueuedApiCall>();
|
|
parallelQueue = new Queue<QueuedApiCall>();
|
|
imageQueue = new Queue<QueuedApiCall>();
|
|
eventQueue = new Queue<Action>();
|
|
|
|
HTTPManager.UseAlternateSSLDefaultValue = true;
|
|
#if !UNITY_EDITOR
|
|
HTTPManager.Logger.Level = Loglevels.All;
|
|
#endif
|
|
|
|
string[] commandLineArgs = Environment.GetCommandLineArgs();
|
|
if (commandLineArgs == null)
|
|
{
|
|
return;
|
|
}
|
|
string[] array = commandLineArgs;
|
|
foreach (string text in array)
|
|
{
|
|
if (text != null && text.StartsWith("+RecNet:", StringComparison.InvariantCultureIgnoreCase))
|
|
{
|
|
RecNetEnv = text.Substring("+RecNet:".Length);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static IPromise<HTTPResponse> Get(Service service, string requestUri, bool executeSerially = true)
|
|
{
|
|
return CallApi(service, requestUri, (Uri uri) => new HTTPRequest(uri, HTTPMethods.Get), executeSerially);
|
|
}
|
|
|
|
public static IPromise<HTTPResponse> Get(string requestUri, bool executeSerially = true)
|
|
{
|
|
return Get(Service.API, requestUri, executeSerially);
|
|
}
|
|
|
|
public static IPromise<HTTPResponse> GetFromCDN(string requestUri)
|
|
{
|
|
return Get(Service.CDN, requestUri, false);
|
|
}
|
|
|
|
public static IPromise<HTTPResponse> GetFromImageCDN(string requestUri)
|
|
{
|
|
return CallApi(Service.Images, requestUri, (Uri uri) => new HTTPRequest(uri, HTTPMethods.Get), imageQueue);
|
|
}
|
|
|
|
public static IPromise<HTTPResponse> Post(Service service, string requestUri, Dictionary<string, string> form, bool executeSerially = true)
|
|
{
|
|
return CallApi(service, requestUri, (Uri uri) => ConstructSimpleFormPost(uri, form), executeSerially);
|
|
}
|
|
|
|
public static IPromise<HTTPResponse> Post(Service service, string requestUri, WWWForm form, bool executeSerially = true)
|
|
{
|
|
return CallApi(service, requestUri, (Uri uri) => ConstructUnityFormPost(uri, form), executeSerially);
|
|
}
|
|
|
|
public static IPromise<HTTPResponse> Post(Service service, string requestUri, HTTPFormBase form, bool executeSerially = true)
|
|
{
|
|
return CallApi(service, requestUri, (Uri uri) => ConstructUnityFormPost(uri, form), executeSerially);
|
|
}
|
|
|
|
public static IPromise<HTTPResponse> Post(Service service, string requestUri, HTTPMultiPartForm multiPartForm, bool executeSerially = true)
|
|
{
|
|
return Post(service, requestUri, (HTTPFormBase)multiPartForm, executeSerially);
|
|
}
|
|
|
|
public static IPromise<HTTPResponse> Post(Service service, string requestUri, string json, bool executeSerially = true)
|
|
{
|
|
return CallApi(service, requestUri, (Uri uri) => ConstructJsonFormPost(uri, json), executeSerially);
|
|
}
|
|
|
|
public static IPromise<HTTPResponse> Post<TRequest>(Service service, string requestUri, TRequest request, bool executeSerially = true) where TRequest : IRecNetRequestObject
|
|
{
|
|
return CallApi(service, requestUri, (Uri uri) => ConstructRecNetObjectPost(uri, request), executeSerially);
|
|
}
|
|
|
|
public static IPromise<HTTPResponse> Post(string requestUri, Dictionary<string, string> form, bool executeSerially = true)
|
|
{
|
|
return Post(Service.API, requestUri, form, executeSerially);
|
|
}
|
|
|
|
public static IPromise<HTTPResponse> Post(string requestUri, WWWForm form, bool executeSerially = true)
|
|
{
|
|
return Post(Service.API, requestUri, form, executeSerially);
|
|
}
|
|
|
|
public static IPromise<HTTPResponse> Post(string requestUri, HTTPFormBase form, bool executeSerially = true)
|
|
{
|
|
return Post(Service.API, requestUri, form, executeSerially);
|
|
}
|
|
|
|
public static IPromise<HTTPResponse> Post(string requestUri, HTTPMultiPartForm multiPartForm, bool executeSerially = true)
|
|
{
|
|
return Post(requestUri, (HTTPFormBase)multiPartForm, executeSerially);
|
|
}
|
|
|
|
public static IPromise<HTTPResponse> Post(string requestUri, string json, bool executeSerially = true)
|
|
{
|
|
return Post(Service.API, requestUri, json, executeSerially);
|
|
}
|
|
|
|
public static IPromise<HTTPResponse> Post<TRequest>(string requestUri, TRequest request, bool executeSerially = true) where TRequest : IRecNetRequestObject
|
|
{
|
|
return Post(Service.API, requestUri, request, executeSerially);
|
|
}
|
|
|
|
public static IPromise<HTTPResponse> PostUsingLoginQueue(Service service, string requestUri, string json)
|
|
{
|
|
return CallApi(service, requestUri, (Uri uri) => ConstructJsonFormPost(uri, json), loginQueue);
|
|
}
|
|
|
|
public static IPromise<HTTPResponse> PostUsingBackgroundImageUploadQueue(Service service, string requestUri, HTTPFormBase form)
|
|
{
|
|
return CallApi(service, requestUri, (Uri uri) => ConstructUnityFormPost(uri, form), imageQueue);
|
|
}
|
|
|
|
public static IPromise<HTTPResponse> PostUsingLoginQueue(string requestUri, string json)
|
|
{
|
|
return PostUsingLoginQueue(Service.API, requestUri, json);
|
|
}
|
|
|
|
public static IPromise<HTTPResponse> PostUsingBackgroundImageUploadQueue(string requestUri, HTTPFormBase form)
|
|
{
|
|
return PostUsingBackgroundImageUploadQueue(Service.API, requestUri, form);
|
|
}
|
|
|
|
public static IEnumerator ConnectToRecNet(ApiCallback callback)
|
|
{
|
|
string url = "https://ns.rec.net/?v=2";
|
|
string log = "Connecting to RecNet";
|
|
|
|
if (!string.IsNullOrEmpty(RecNetEnv))
|
|
{
|
|
url = $"{url}&e={Uri.EscapeDataString(RecNetEnv)}";
|
|
log = $"{log} {RecNetEnv}";
|
|
}
|
|
|
|
Debug.Log(log);
|
|
|
|
HTTPRequest request = new HTTPRequest(new Uri(url), HTTPMethods.Get);
|
|
yield return request.Send();
|
|
|
|
string error = GetError(request, true);
|
|
if (!string.IsNullOrEmpty(error))
|
|
{
|
|
request = new HTTPRequest(new Uri("http://www.google.com/generate_204"), HTTPMethods.Get);
|
|
yield return request.Send();
|
|
if (request.Response != null && request.Response.StatusCode == 204)
|
|
{
|
|
Debug.LogError($"RecNet name server query failed (error code: summer): {error}");
|
|
SafeInvoke(callback, "Failed to connect to RecNet (error code: summer)");
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError($"Network connectivity test failed: {GetError(request, true)}");
|
|
SafeInvoke(callback, "No internet connection");
|
|
}
|
|
yield break;
|
|
}
|
|
try
|
|
{
|
|
NameServerResponse nameServerResponse = JsonUtility.FromJson<NameServerResponse>(request.Response.DataAsText);
|
|
|
|
if (nameServerResponse.RecNetStatus != null)
|
|
{
|
|
Debug.LogError(nameServerResponse.RecNetStatus);
|
|
SafeInvoke(callback, nameServerResponse.RecNetStatus);
|
|
yield break;
|
|
}
|
|
|
|
TrySetServiceUri(Service.Auth, nameServerResponse.Auth);
|
|
TrySetServiceUri(Service.API, nameServerResponse.API);
|
|
TrySetServiceUri(Service.WWW, nameServerResponse.WWW);
|
|
TrySetServiceUri(Service.Commerce, nameServerResponse.Commerce);
|
|
TrySetServiceUri(Service.Matchmaking, nameServerResponse.API);
|
|
TrySetServiceUri(Service.Notifications, nameServerResponse.Notifications);
|
|
TrySetServiceUri(Service.Images, nameServerResponse.Images);
|
|
TrySetServiceUri(Service.CDN, nameServerResponse.CDN);
|
|
TrySetServiceUri(Service.Storage, nameServerResponse.API);
|
|
TrySetServiceUri(Service.Chat, nameServerResponse.API);
|
|
TrySetServiceUri(Service.Leaderboard, nameServerResponse.API);
|
|
TrySetServiceUri(Service.Accounts, nameServerResponse.API);
|
|
TrySetServiceUri(Service.Rooms, nameServerResponse.API);
|
|
|
|
Service[] servicesRequiringAuth = new[]
|
|
{
|
|
Service.Auth,
|
|
Service.API,
|
|
Service.Commerce,
|
|
Service.Matchmaking,
|
|
Service.Notifications,
|
|
//Service.Images,
|
|
//Service.CDN,
|
|
Service.Storage,
|
|
Service.Chat,
|
|
Service.Leaderboard,
|
|
Service.Accounts,
|
|
Service.Rooms
|
|
};
|
|
|
|
HostsRequiringRecNetAccessToken = new HashSet<string>();
|
|
|
|
foreach (Service service in servicesRequiringAuth)
|
|
if (ServiceUris.ContainsKey(service) && ServiceUris[service] != null)
|
|
HostsRequiringRecNetAccessToken.Add(ServiceUris[service].Host);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
Debug.LogException(exception);
|
|
SafeInvoke(callback, "Failed to connect to RecNet (error code: locker)");
|
|
yield break;
|
|
}
|
|
|
|
var requestUri = $"api/versioncheck/v3?v={Uri.EscapeDataString(BuildSettings.Version)}&p={Convert.ToInt32(PlatformManager.Instance.CurrentPlatform)}";
|
|
|
|
yield return Get(requestUri).ExpectResponse<VersionCheckResponse>().Error(delegate (string err)
|
|
{
|
|
Debug.LogError($"RecNet version check failed: {err}");
|
|
SafeInvoke(callback, "Failed to connect to RecNet (error code: student)");
|
|
}).Then(delegate (VersionCheckResponse response)
|
|
{
|
|
if (!response.ValidVersion)
|
|
SafeInvoke(callback, "Rec Room update required");
|
|
|
|
else
|
|
SafeInvoke(callback, null);
|
|
});
|
|
}
|
|
|
|
private static void LazyInit()
|
|
{
|
|
if (!initialized)
|
|
{
|
|
HTTPUpdateDelegator.OnBeforeApplicationQuit = delegate
|
|
{
|
|
Notifications.Disconnect();
|
|
Login.Logout();
|
|
Thread.Sleep(1000);
|
|
return true;
|
|
};
|
|
|
|
Notifications.Initialize();
|
|
GameSessions.Initialize();
|
|
Images.Initialize();
|
|
Messages.Initialize();
|
|
Moderation.Initialize();
|
|
Presence.Initialize();
|
|
Profiles.Initialize();
|
|
Relationships.Initialize();
|
|
PlayerSubscriptions.Initialize();
|
|
PlayerReporting.Initialize();
|
|
Avatars.Initialize();
|
|
Rooms.Initialize();
|
|
Storefronts.Initialize();
|
|
Consumables.Initialize();
|
|
PlayerEvents.Initialize();
|
|
CommunityBoard.Initialize();
|
|
Chats.Initialize();
|
|
Login.Initialize();
|
|
|
|
GameObject gameObject = new GameObject("RecNetCoreCoroutineObject");
|
|
UnityEngine.Object.DontDestroyOnLoad(gameObject);
|
|
gameObject.hideFlags = HideFlags.HideInHierarchy;
|
|
|
|
RecNetCore recNetCore = gameObject.AddComponent<RecNetCore>();
|
|
recNetCore.StartCoroutine(ProcessCallApiQueue(loginQueue));
|
|
recNetCore.StartCoroutine(ProcessCallApiQueue(serialQueue));
|
|
recNetCore.StartCoroutine(PeriodicRefreshLogin());
|
|
|
|
for (int num = 0; num < PARALLEL_WORKER_THREAD_COUNT; num++)
|
|
recNetCore.StartCoroutine(ProcessCallApiQueue(parallelQueue));
|
|
|
|
for (int num2 = 0; num2 < PARALLEL_WORKER_THREAD_COUNT; num2++)
|
|
recNetCore.StartCoroutine(ProcessCallApiQueue(imageQueue));
|
|
|
|
recNetCore.StartCoroutine(ProcessEventQueue());
|
|
initialized = true;
|
|
}
|
|
}
|
|
|
|
private static void TrySetServiceUri(Service service, string uri)
|
|
{
|
|
if (!string.IsNullOrEmpty(uri))
|
|
ServiceUris[service] = new Uri(uri);
|
|
}
|
|
|
|
private static float GetExponentialBackoffTime(int retryCount)
|
|
=> (retryCount <= 0) ? 0f : ((float)(1 << Mathf.Min(retryCount - 1, MAX_BACKOFF_EXPONENT)));
|
|
|
|
private static HTTPRequest ConstructSimpleFormPost(Uri uri, Dictionary<string, string> form)
|
|
{
|
|
var request = new HTTPRequest(uri, HTTPMethods.Post);
|
|
foreach (KeyValuePair<string, string> item in form)
|
|
request.AddField(item.Key, item.Value);
|
|
|
|
return request;
|
|
}
|
|
|
|
private static HTTPRequest ConstructUnityFormPost(Uri uri, WWWForm form)
|
|
{
|
|
HTTPRequest hTTPRequest = new HTTPRequest(uri, HTTPMethods.Post);
|
|
hTTPRequest.SetFields(form);
|
|
return hTTPRequest;
|
|
}
|
|
|
|
private static HTTPRequest ConstructUnityFormPost(Uri uri, HTTPFormBase form)
|
|
{
|
|
HTTPRequest hTTPRequest = new HTTPRequest(uri, HTTPMethods.Post);
|
|
hTTPRequest.SetForm(form);
|
|
return hTTPRequest;
|
|
}
|
|
|
|
private static HTTPRequest ConstructJsonFormPost(Uri uri, string json)
|
|
{
|
|
HTTPRequest hTTPRequest = new HTTPRequest(uri, HTTPMethods.Post);
|
|
hTTPRequest.SetHeader("Content-Type", "application/json");
|
|
hTTPRequest.RawData = Encoding.UTF8.GetBytes(json);
|
|
return hTTPRequest;
|
|
}
|
|
|
|
private static HTTPRequest ConstructRecNetObjectPost(Uri uri, IRecNetRequestObject request)
|
|
{
|
|
Dictionary<string, object> obj = request.Serialize();
|
|
string json = Json.Serialize(obj);
|
|
return ConstructJsonFormPost(uri, json);
|
|
}
|
|
|
|
private static void AddRecNetAuthToken(HTTPRequest request)
|
|
{
|
|
if (RequiresRecNetAccessToken(request.CurrentUri))
|
|
{
|
|
if (Login.AccessToken != null)
|
|
request.SetHeader("Authorization", $"Bearer {Login.AccessToken}");
|
|
|
|
request.OnBeforeRedirection += ClearRecNetAuthToken;
|
|
}
|
|
}
|
|
|
|
private static bool ClearRecNetAuthToken(HTTPRequest request, HTTPResponse response, Uri redirectUri)
|
|
{
|
|
if (!RequiresRecNetAccessToken(redirectUri))
|
|
{
|
|
request.RemoveHeader("Authorization");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public static Uri GetServiceUri(Service service)
|
|
{
|
|
ServiceUris.TryGetValue(service, out var uri);
|
|
return uri;
|
|
}
|
|
|
|
private static bool RequiresRecNetAccessToken(Uri uri) => HostsRequiringRecNetAccessToken.Contains(uri.Host);
|
|
|
|
private static IEnumerator PeriodicRefreshLogin()
|
|
{
|
|
WaitForSeconds wait = new WaitForSeconds(REFRESH_LOGIN_INTERVAL);
|
|
while (true)
|
|
{
|
|
yield return wait;
|
|
if (Login.AccessToken != null)
|
|
{
|
|
yield return Login.RefreshLogin();
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void SetLocalProfileId(long profileId)
|
|
{
|
|
LocalProfileId = profileId;
|
|
}
|
|
|
|
private static IPromise<HTTPResponse> CallApi(Service service, string uri, ConstructRequestCallback constructRequest, bool executeSerially)
|
|
{
|
|
Queue<QueuedApiCall> queue = executeSerially ? serialQueue : parallelQueue;
|
|
return CallApi(service, uri, constructRequest, queue);
|
|
}
|
|
|
|
private static IPromise<HTTPResponse> CallApi(Service service, string uri, ConstructRequestCallback constructRequest, Queue<QueuedApiCall> queue)
|
|
{
|
|
LazyInit();
|
|
QueuedApiCall queuedApiCall = new QueuedApiCall(service, uri, constructRequest);
|
|
queue.Enqueue(queuedApiCall);
|
|
return queuedApiCall.Promise;
|
|
}
|
|
|
|
private static IPromise<HTTPResponse> CallApi(Service service, string uri, AsyncConstructRequestCallback asyncConstructRequest, Queue<QueuedApiCall> queue)
|
|
{
|
|
LazyInit();
|
|
QueuedApiCall queuedApiCall = new QueuedApiCall(service, uri, asyncConstructRequest);
|
|
queue.Enqueue(queuedApiCall);
|
|
return queuedApiCall.Promise;
|
|
}
|
|
|
|
private static IEnumerator ProcessCallApiQueue(Queue<QueuedApiCall> queue)
|
|
{
|
|
int consecutiveRetries = 0;
|
|
float previousErrorTime = 0f;
|
|
|
|
while (true)
|
|
{
|
|
yield return new WaitUntil(() => queue.Count > 0);
|
|
if (queue.Count == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
QueuedApiCall apiCall = queue.Dequeue();
|
|
|
|
Uri baseUri = GetServiceUri(apiCall.Service);
|
|
if (baseUri == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Uri uri = new Uri(baseUri, apiCall.Uri);
|
|
|
|
HTTPRequest request = null;
|
|
|
|
for (int retry = 0; retry < API_RETRIES; retry++)
|
|
{
|
|
if (request != null)
|
|
{
|
|
request.Dispose();
|
|
request = null;
|
|
}
|
|
|
|
if (consecutiveRetries > 0)
|
|
{
|
|
float exponentialBackoff = GetExponentialBackoffTime(consecutiveRetries);
|
|
float timeToWait = exponentialBackoff - (Time.realtimeSinceStartup - previousErrorTime);
|
|
if (timeToWait > 0f)
|
|
{
|
|
yield return new WaitForSecondsRealtime(timeToWait);
|
|
}
|
|
}
|
|
|
|
if (apiCall.ConstructRequest != null)
|
|
{
|
|
try
|
|
{
|
|
request = apiCall.ConstructRequest(uri);
|
|
if (queue == imageQueue)
|
|
{
|
|
request.Priority = -1;
|
|
}
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
Debug.LogException(exception);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
yield return apiCall.AsyncConstructRequest(uri, delegate (HTTPRequest result)
|
|
{
|
|
request = result;
|
|
});
|
|
}
|
|
|
|
if (request == null)
|
|
{
|
|
break;
|
|
}
|
|
|
|
AddRecNetAuthToken(request);
|
|
yield return request.Send();
|
|
|
|
if (request.Response == null || request.Response.StatusCode == 429 ||
|
|
(request.Response.StatusCode >= 500 && request.Response.StatusCode < 600))
|
|
{
|
|
consecutiveRetries++;
|
|
previousErrorTime = Time.realtimeSinceStartup;
|
|
continue;
|
|
}
|
|
|
|
if (request.Response != null && request.Response.StatusCode == 401)
|
|
{
|
|
consecutiveRetries++;
|
|
previousErrorTime = Time.realtimeSinceStartup;
|
|
}
|
|
else
|
|
{
|
|
consecutiveRetries = 0;
|
|
}
|
|
break;
|
|
}
|
|
|
|
try
|
|
{
|
|
string error = GetError(request, false);
|
|
if (error == null)
|
|
{
|
|
apiCall.Promise.Complete(request.Response);
|
|
}
|
|
else
|
|
{
|
|
apiCall.Promise.Error(error);
|
|
}
|
|
}
|
|
catch (Exception exception2)
|
|
{
|
|
Debug.LogException(exception2);
|
|
}
|
|
|
|
request?.Dispose();
|
|
request = null;
|
|
}
|
|
}
|
|
|
|
public static string GetError(HTTPRequest request, bool handleHttpStatusCodes)
|
|
{
|
|
if (request == null)
|
|
{
|
|
return "Failed to construct Web request";
|
|
}
|
|
switch (request.State)
|
|
{
|
|
case HTTPRequestStates.Aborted:
|
|
return "Aborted";
|
|
case HTTPRequestStates.ConnectionTimedOut:
|
|
return "Connection timed out";
|
|
case HTTPRequestStates.TimedOut:
|
|
return "Response timed out";
|
|
case HTTPRequestStates.Error:
|
|
Debug.LogException(request.Exception);
|
|
return (request.Exception == null) ? "Unspecified error" : request.Exception.Message;
|
|
case HTTPRequestStates.Finished:
|
|
return (!handleHttpStatusCodes || request.Response.StatusCode < 400) ? null : ("HTTP Error " + request.Response.StatusCode);
|
|
default:
|
|
Debug.LogError("HTTPRequest is in an invalid state: " + request.State);
|
|
return "Unexpected state";
|
|
}
|
|
}
|
|
|
|
public static void SafeInvoke(ApiCallback callback, string error)
|
|
{
|
|
try
|
|
{
|
|
if (callback != null)
|
|
{
|
|
callback(error);
|
|
}
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
Debug.LogException(exception);
|
|
}
|
|
}
|
|
|
|
public static void SafeInvoke<T>(ApiCallback<T> callback, string error, T response)
|
|
{
|
|
try
|
|
{
|
|
if (callback != null)
|
|
{
|
|
callback(error, response);
|
|
}
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
Debug.LogException(exception);
|
|
}
|
|
}
|
|
|
|
public static void DispatchOnUnityThread(Action callback)
|
|
{
|
|
lock (eventQueue)
|
|
{
|
|
eventQueue.Enqueue(callback);
|
|
}
|
|
}
|
|
|
|
public static IEnumerator RunOnBackgroundThread(Action callback)
|
|
{
|
|
bool complete = false;
|
|
Thread thread = new Thread((ThreadStart)delegate
|
|
{
|
|
try
|
|
{
|
|
callback();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Exception ex2 = ex;
|
|
Exception ex3 = ex2;
|
|
DispatchOnUnityThread(delegate
|
|
{
|
|
Debug.LogException(ex3);
|
|
});
|
|
}
|
|
finally
|
|
{
|
|
complete = true;
|
|
}
|
|
});
|
|
thread.Start();
|
|
return new WaitUntil(() => complete);
|
|
}
|
|
|
|
private static IEnumerator ProcessEventQueue()
|
|
{
|
|
while (true)
|
|
{
|
|
yield return new WaitUntil(() => eventQueue.Count > 0);
|
|
Action callback;
|
|
lock (eventQueue)
|
|
{
|
|
callback = eventQueue.Dequeue();
|
|
}
|
|
try
|
|
{
|
|
callback();
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
Debug.LogException(exception);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |