using System; using System.Net; using System.IO; using System.Text; using GameAnalyticsSDK.Net.Logging; using GameAnalyticsSDK.Net.Utilities; using GameAnalyticsSDK.Net.State; using GameAnalyticsSDK.Net.Validators; using System.Collections.Generic; using GameAnalyticsSDK.Net.Tasks; #if !WINDOWS_UWP && !WINDOWS_WSA using System.Security.Cryptography.X509Certificates; using System.Net.Security; #endif #if WINDOWS_UWP || WINDOWS_WSA using System.Threading.Tasks; using Windows.Networking.Connectivity; using GameAnalyticsSDK.Net.Device; #endif namespace GameAnalyticsSDK.Net.Http { internal class GAHTTPApi { #region Fields and properties private static readonly GAHTTPApi _instance = new GAHTTPApi(); // base url settings private static string protocol = "https"; private static string hostName = "api.gameanalytics.com"; private static string version = "v2"; private static string baseUrl = getBaseUrl(); private static string initializeUrlPath = "init"; private static string eventsUrlPath = "events"; private bool useGzip; private static string getBaseUrl() { return protocol + "://" + hostName + "/" + version; } public static GAHTTPApi Instance { get { return _instance; } } #endregion // Fields and properties // Constructor - setup the basic information for HTTP private GAHTTPApi() { this.useGzip = true; #if DEBUG this.useGzip = false; #endif #if !WINDOWS_UWP && !WINDOWS_WSA ServicePointManager.ServerCertificateValidationCallback = MyRemoteCertificateValidationCallback; #endif #if WINDOWS_UWP || WINDOWS_WSA NetworkInformation.NetworkStatusChanged += NetworkInformationOnNetworkStatusChanged; CheckInternetAccess(); #endif } #if WINDOWS_UWP || WINDOWS_WSA private static void NetworkInformationOnNetworkStatusChanged(object sender) { CheckInternetAccess(); } private static void CheckInternetAccess() { var connectionProfile = NetworkInformation.GetInternetConnectionProfile(); bool hasInternetAccess = (connectionProfile != null && connectionProfile.GetNetworkConnectivityLevel() == NetworkConnectivityLevel.InternetAccess); if (hasInternetAccess) { if (connectionProfile.IsWlanConnectionProfile) { GADevice.ConnectionType = "wifi"; } else if (connectionProfile.IsWwanConnectionProfile) { GADevice.ConnectionType = "wwan"; } else { GADevice.ConnectionType = "lan"; } } else { GADevice.ConnectionType = "offline"; } } #endif #if !WINDOWS_UWP && !WINDOWS_WSA private bool MyRemoteCertificateValidationCallback(System.Object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { bool isOk = true; // If there are errors in the certificate chain, look at each error to determine the cause. if (sslPolicyErrors != SslPolicyErrors.None) { for (int i = 0; i < chain.ChainStatus.Length; i++) { if (chain.ChainStatus[i].Status != X509ChainStatusFlags.RevocationStatusUnknown) { chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain; chain.ChainPolicy.RevocationMode = X509RevocationMode.Online; chain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 1, 0); chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllFlags; bool chainIsValid = chain.Build((X509Certificate2)certificate); if (!chainIsValid) { isOk = false; } } } } return isOk; } #endif #region Public methods #if WINDOWS_UWP || WINDOWS_WSA public async Task> RequestInitReturningDict() #else public KeyValuePair RequestInitReturningDict() #endif { JSONObject json; EGAHTTPApiResponse result = EGAHTTPApiResponse.NoResponse; string gameKey = GAState.GameKey; // Generate URL string url = baseUrl + "/" + gameKey + "/" + initializeUrlPath; url = "https://rubick.gameanalytics.com/v2/command_center?game_key=" + gameKey + "&interval_seconds=1000000"; //url = "https://requestb.in/1fvbe2g1"; GALogger.D("Sending 'init' URL: " + url); JSONObject initAnnotations = GAState.GetInitAnnotations(); // make JSON string from data string JSONstring = initAnnotations.ToString(); if (string.IsNullOrEmpty(JSONstring)) { result = EGAHTTPApiResponse.JsonEncodeFailed; json = null; return new KeyValuePair(result, json); } string body = ""; HttpStatusCode responseCode = (HttpStatusCode)0; string responseDescription = ""; string authorization = ""; try { byte[] payloadData = CreatePayloadData(JSONstring, false); HttpWebRequest request = CreateRequest(url, payloadData, false); authorization = request.Headers[HttpRequestHeader.Authorization]; #if WINDOWS_UWP || WINDOWS_WSA using (Stream dataStream = await request.GetRequestStreamAsync()) #else using(Stream dataStream = request.GetRequestStream()) #endif { dataStream.Write(payloadData, 0, payloadData.Length); } #if WINDOWS_UWP || WINDOWS_WSA using (HttpWebResponse response = await request.GetResponseAsync() as HttpWebResponse) #else using(HttpWebResponse response = request.GetResponse() as HttpWebResponse) #endif { using (Stream dataStream = response.GetResponseStream()) { using (StreamReader reader = new StreamReader(dataStream)) { string responseString = reader.ReadToEnd(); responseCode = response.StatusCode; responseDescription = response.StatusDescription; // print result body = responseString; } } } } catch (WebException e) { if(e.Response != null) { using (HttpWebResponse response = (HttpWebResponse)e.Response) { using (Stream streamResponse = response.GetResponseStream()) { using (StreamReader streamRead = new StreamReader(streamResponse)) { string responseString = streamRead.ReadToEnd(); responseCode = response.StatusCode; responseDescription = response.StatusDescription; body = responseString; } } } } } catch(Exception e) { GALogger.E(e.ToString()); } // process the response GALogger.D("init request content : " + body); JSONNode requestJsonDict = JSON.Parse(body); EGAHTTPApiResponse requestResponseEnum = ProcessRequestResponse(responseCode, responseDescription, body, "Init"); // if not 200 result if (requestResponseEnum != EGAHTTPApiResponse.Ok && requestResponseEnum != EGAHTTPApiResponse.BadRequest) { GALogger.D("Failed Init Call. URL: " + url + ", Authorization: " + authorization + ", JSONString: " + JSONstring); result = requestResponseEnum; json = null; return new KeyValuePair(result, json); } if (requestJsonDict == null) { GALogger.D("Failed Init Call. Json decoding failed"); result = EGAHTTPApiResponse.JsonDecodeFailed; json = null; return new KeyValuePair(result, json); } // print reason if bad request if (requestResponseEnum == EGAHTTPApiResponse.BadRequest) { GALogger.D("Failed Init Call. Bad request. Response: " + requestJsonDict.ToString()); // return bad request result result = requestResponseEnum; json = null; return new KeyValuePair(result, json); } // validate Init call values JSONObject validatedInitValues = GAValidator.ValidateAndCleanInitRequestResponse(requestJsonDict); if (validatedInitValues == null) { result = EGAHTTPApiResponse.BadResponse; json = null; return new KeyValuePair(result, json); } // all ok result = EGAHTTPApiResponse.Ok; json = validatedInitValues; return new KeyValuePair(result, json); } #if WINDOWS_UWP || WINDOWS_WSA public async Task> SendEventsInArray(List eventArray) #else public KeyValuePair SendEventsInArray(List eventArray) #endif { JSONNode json; if (eventArray.Count == 0) { GALogger.D("sendEventsInArray called with missing eventArray"); } EGAHTTPApiResponse result = EGAHTTPApiResponse.NoResponse; string gameKey = GAState.GameKey; // Generate URL string url = baseUrl + "/" + gameKey + "/" + eventsUrlPath; GALogger.D("Sending 'events' URL: " + url); // make JSON string from data string JSONstring = GAUtilities.ArrayOfObjectsToJsonString(eventArray); if (JSONstring.Length == 0) { GALogger.D("sendEventsInArray JSON encoding failed of eventArray"); json = null; result = EGAHTTPApiResponse.JsonEncodeFailed; return new KeyValuePair(result, json); } string body = ""; HttpStatusCode responseCode = (HttpStatusCode)0; string responseDescription = ""; string authorization = ""; try { byte[] payloadData = CreatePayloadData(JSONstring, useGzip); HttpWebRequest request = CreateRequest(url, payloadData, useGzip); authorization = request.Headers[HttpRequestHeader.Authorization]; #if WINDOWS_UWP || WINDOWS_WSA using (Stream dataStream = await request.GetRequestStreamAsync()) #else using(Stream dataStream = request.GetRequestStream()) #endif { dataStream.Write(payloadData, 0, payloadData.Length); } #if WINDOWS_UWP || WINDOWS_WSA using (HttpWebResponse response = await request.GetResponseAsync() as HttpWebResponse) #else using(HttpWebResponse response = request.GetResponse() as HttpWebResponse) #endif { using (Stream dataStream = response.GetResponseStream()) { using (StreamReader reader = new StreamReader(dataStream)) { string responseString = reader.ReadToEnd(); responseCode = response.StatusCode; responseDescription = response.StatusDescription; // print result body = responseString; } } } } catch (WebException e) { if(e.Response != null) { using (HttpWebResponse response = (HttpWebResponse)e.Response) { using (Stream streamResponse = response.GetResponseStream()) { using (StreamReader streamRead = new StreamReader(streamResponse)) { string responseString = streamRead.ReadToEnd(); responseCode = response.StatusCode; responseDescription = response.StatusDescription; body = responseString; } } } } } catch(Exception e) { GALogger.E(e.ToString()); } GALogger.D("events request content: " + body); EGAHTTPApiResponse requestResponseEnum = ProcessRequestResponse(responseCode, responseDescription, body, "Events"); // if not 200 result if (requestResponseEnum != EGAHTTPApiResponse.Ok && requestResponseEnum != EGAHTTPApiResponse.BadRequest) { GALogger.D("Failed events Call. URL: " + url + ", Authorization: " + authorization + ", JSONString: " + JSONstring); json = null; result = requestResponseEnum; return new KeyValuePair(result, json); } // decode JSON JSONNode requestJsonDict = JSON.Parse(body); if (requestJsonDict == null) { json = null; result = EGAHTTPApiResponse.JsonDecodeFailed; return new KeyValuePair(result, json); } // print reason if bad request if (requestResponseEnum == EGAHTTPApiResponse.BadRequest) { GALogger.D("Failed Events Call. Bad request. Response: " + requestJsonDict.ToString()); } // return response json = requestJsonDict; result = requestResponseEnum; return new KeyValuePair(result, json); } public void SendSdkErrorEvent(EGASdkErrorType type) { if(!GAState.IsEventSubmissionEnabled) { return; } string gameKey = GAState.GameKey; string secretKey = GAState.GameSecret; // Validate if (!GAValidator.ValidateSdkErrorEvent(gameKey, secretKey, type)) { return; } // Generate URL string url = baseUrl + "/" + gameKey + "/" + eventsUrlPath; GALogger.D("Sending 'events' URL: " + url); string payloadJSONString = ""; JSONObject json = GAState.GetSdkErrorEventAnnotations(); string typeString = SdkErrorTypeToString(type); json.Add("type", typeString); List eventArray = new List(); eventArray.Add(json); payloadJSONString = GAUtilities.ArrayOfObjectsToJsonString(eventArray); if(string.IsNullOrEmpty(payloadJSONString)) { GALogger.W("sendSdkErrorEvent: JSON encoding failed."); return; } GALogger.D("sendSdkErrorEvent json: " + payloadJSONString); byte[] payloadData = Encoding.UTF8.GetBytes(payloadJSONString); SdkErrorTask sdkErrorTask = new SdkErrorTask(type, payloadData, secretKey); sdkErrorTask.Execute(url); } #endregion // Public methods #region Private methods private byte[] CreatePayloadData(string payload, bool gzip) { byte[] payloadData; if(gzip) { payloadData = GAUtilities.GzipCompress(payload); GALogger.D("Gzip stats. Size: " + Encoding.UTF8.GetBytes(payload).Length + ", Compressed: " + payloadData.Length + ", Content: " + payload); } else { payloadData = Encoding.UTF8.GetBytes(payload); } return payloadData; } private static string SdkErrorTypeToString(EGASdkErrorType value) { switch(value) { case EGASdkErrorType.Rejected: { return "rejected"; } default: { return ""; } } } private HttpWebRequest CreateRequest(string url, byte[] payloadData, bool gzip) { HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest; request.Method = "POST"; #if WINDOWS_UWP request.Headers[HttpRequestHeader.ContentLength] = payloadData.Length.ToString(); #elif WINDOWS_WSA //request.Headers[HttpRequestHeader.ContentLength] = payloadData.Length.ToString(); // Bug setting Content Length on WSA #else request.ContentLength = payloadData.Length; #endif if (gzip) { request.Headers[HttpRequestHeader.ContentEncoding] = "gzip"; } // create authorization hash String key = GAState.GameSecret; request.Headers[HttpRequestHeader.Authorization] = GAUtilities.HmacWithKey(key, payloadData); request.ContentType = "application/json"; return request; } private EGAHTTPApiResponse ProcessRequestResponse(HttpStatusCode responseCode, string responseMessage, string body, string requestId) { // if no result - often no connection if(string.IsNullOrEmpty(body)) { GALogger.D(requestId + " request. failed. Might be no connection. Description: " + responseMessage + ", Status code: " + responseCode); return EGAHTTPApiResponse.NoResponse; } // ok if (responseCode == HttpStatusCode.OK) { return EGAHTTPApiResponse.Ok; } // 401 can return 0 status if (responseCode == (HttpStatusCode)0 || responseCode == HttpStatusCode.Unauthorized) { GALogger.D(requestId + " request. 401 - Unauthorized."); return EGAHTTPApiResponse.Unauthorized; } if (responseCode == HttpStatusCode.BadRequest) { GALogger.D(requestId + " request. 400 - Bad Request."); return EGAHTTPApiResponse.BadRequest; } if (responseCode == HttpStatusCode.InternalServerError) { GALogger.D(requestId + " request. 500 - Internal Server Error."); return EGAHTTPApiResponse.InternalServerError; } return EGAHTTPApiResponse.UnknownResponseCode; } #endregion // Private methods } }