// I have a refresh token, I need an access token. public IEnumerator <object> Reauthorize(Action successCallback, Action <string> failureCallback) { m_AccessToken = null; if (!String.IsNullOrEmpty(m_RefreshToken)) { Dictionary <string, string> parameters = new Dictionary <string, string>(); parameters.Add("client_id", m_ClientId); parameters.Add("client_secret", m_ClientSecret); parameters.Add("refresh_token", m_RefreshToken); parameters.Add("grant_type", "refresh_token"); using (UnityWebRequest www = UnityWebRequest.Post(m_AccessTokenUri, parameters)) { yield return(UnityCompat.SendWebRequest(www)); if (UnityCompat.IsNetworkError(www)) { failureCallback("Network error"); yield break; } if (www.responseCode == 400 || www.responseCode == 401) { // Refresh token revoked or expired - forget it m_RefreshToken = null; PlayerPrefs.DeleteKey(m_PlayerPrefRefreshKey); failureCallback("No valid refresh token, could not reauthorize"); } else { JObject json = JObject.Parse(www.downloadHandler.text); m_AccessToken = json["access_token"].ToString(); successCallback(); } } } }
private IEnumerator LoadProfileIcon(string uri) { if (Profile == null) { yield break; } using (UnityWebRequest www = UnityCompat.GetTexture(uri + kIconSizeSuffix)) { yield return(UnityCompat.SendWebRequest(www)); if (UnityCompat.IsNetworkError(www) || www.responseCode >= 400) { Debug.LogErrorFormat("Error downloading {0}, error {1}", uri, www.responseCode); Profile.icon = null; } else { // Convert the texture to a circle and set it as the user's avatar in the UI. Texture2D profileImage = DownloadHandlerTexture.GetContent(www); Profile.icon = Sprite.Create(CropSquareTextureToCircle(profileImage), new Rect(0, 0, profileImage.width, profileImage.height), new Vector2(0.5f, 0.5f), USER_AVATAR_PIXELS_PER_UNIT); } if (m_OnProfileUpdated != null) { m_OnProfileUpdated(); } } }
private IEnumerator GetUserInfo() { if (String.IsNullOrEmpty(m_RefreshToken)) { yield break; } UserInfo user = new UserInfo(); for (int i = 0; i < 2; i++) { using (UnityWebRequest www = UnityWebRequest.Get(m_UserInfoUri)) { Authenticate(www); yield return(UnityCompat.SendWebRequest(www)); if (www.responseCode == 200) { JObject json = JObject.Parse(www.downloadHandler.text); user.id = json["resourceName"].ToString(); user.name = json["names"][0]["displayName"].ToString(); string iconUri = json["photos"][0]["url"].ToString(); if (json["residences"] != null) { user.location = json["residences"][0]["value"].ToString(); } if (json["emailAddresses"] != null) { foreach (var email in json["emailAddresses"]) { var primary = email["metadata"]["primary"]; if (primary != null && primary.Value <bool>()) { user.email = email["value"].ToString(); break; } } } Profile = user; yield return(LoadProfileIcon(iconUri)); yield break; } else if (www.responseCode == 401) { yield return(Reauthorize(() => { }, (error) => { Debug.LogError(error); })); } else { Debug.Log(www.responseCode); Debug.Log(www.error); Debug.Log(www.downloadHandler.text); } } } Profile = null; }
/// <summary> /// Sends a hit to Google Analytics. /// </summary> /// <param name="fields">Key-value pairs that make up the properties of the hit. These /// are assumed to be in the appropriate Google Analytics format.</param> private void SendHit(Dictionary <string, string> fields) { StringBuilder sb = new StringBuilder(prefix); foreach (KeyValuePair <string, string> pair in fields) { sb.AppendFormat("&{0}={1}", UnityWebRequest.EscapeURL(pair.Key), UnityWebRequest.EscapeURL(pair.Value)); } string payload = sb.ToString(); try { UnityWebRequest request = new UnityWebRequest("https://www.google-analytics.com/collect", "POST"); request.uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(payload)); request.uploadHandler.contentType = "application/x-www-form-urlencoded"; UnityCompat.SendWebRequest(request); PtDebug.LogFormat("ANALYTICS: sent hit: {0}", payload); } catch (Exception ex) { // Reporting these as errors would be noisy and annoying. We don't want to do that -- maybe the user is // offline. Not being able to send analytics isn't a big deal. So only log this error if // PtDebug verbose logging is on. PtDebug.LogFormat("*** Error sending analytics: {0}", ex); } }
/// I have nothing. Open browser to authorize permissions then get refresh and access tokens. public IEnumerator <object> Authorize(System.Action onSuccess, System.Action onFailure, bool launchSignInFlowIfNeeded) { if (String.IsNullOrEmpty(m_RefreshToken)) { if (!launchSignInFlowIfNeeded) { // We need to launch the sign-in flow. If we are not allowed to, then we have failed. onFailure(); yield break; } if (m_HttpListener == null) { // Listener is not yet running. Let's try to start it. if (!StartHttpListener(out m_HttpPort, out m_HttpListener)) { // Failed to start HTTP listener. Debug.LogError("Failed to start HTTP listener for authentication flow."); if (onFailure != null) { onFailure(); } yield break; } // HTTP listener successfully started. } StringBuilder sb = new StringBuilder(m_RequestTokenUri) .Append("?client_id=").Append(Uri.EscapeDataString(m_ClientId)) .Append("&redirect_uri=").Append("http://localhost:").Append(m_HttpPort).Append(m_CallbackPath) .Append("&response_type=code") .Append("&scope=").Append(m_OAuthScope); // Something about the url makes OpenURL() not work on OSX, so use a workaround if (Application.platform == RuntimePlatform.OSXEditor || Application.platform == RuntimePlatform.OSXPlayer) { System.Diagnostics.Process.Start(sb.ToString()); } else { Application.OpenURL(sb.ToString()); } if (m_WaitingOnAuthorization) { // A previous attempt is already waiting yield break; } m_WaitingOnAuthorization = true; m_VerificationCode = null; m_VerificationError = false; // Wait for verification while (m_VerificationCode == null && !m_VerificationError) { yield return(null); } if (m_VerificationError) { Debug.LogError("Account verification failed"); Debug.LogFormat("Verification error {0}", m_VerificationCode); m_WaitingOnAuthorization = false; yield break; } // Exchange for tokens var parameters = new Dictionary <string, string>(); parameters.Add("code", m_VerificationCode); parameters.Add("client_id", m_ClientId); parameters.Add("client_secret", m_ClientSecret); parameters.Add("redirect_uri", String.Format("http://localhost:{0}{1}", m_HttpPort, m_CallbackPath)); parameters.Add("grant_type", "authorization_code"); UnityWebRequest www = UnityWebRequest.Post(m_AccessTokenUri, parameters); yield return(UnityCompat.SendWebRequest(www)); if (UnityCompat.IsNetworkError(www)) { Debug.LogError("Network error"); m_WaitingOnAuthorization = false; yield break; } else if (www.responseCode >= 400) { Debug.LogError("Authorization failed"); Debug.LogFormat("Authorization error {0}", www.downloadHandler.text); m_WaitingOnAuthorization = false; yield break; } JObject json = JObject.Parse(www.downloadHandler.text); if (json != null) { SetTokens(json["access_token"].ToString(), json["refresh_token"].ToString()); } m_WaitingOnAuthorization = false; } yield return(GetUserInfo()); if (LoggedIn) { onSuccess(); } else { onFailure(); } }
/// <summary> /// Co-routine that services one PendingRequest. This method must be called with StartCoroutine. /// </summary> /// <param name="request">The request to service.</param> private IEnumerator HandleWebRequest(PendingRequest request, BufferHolder bufferHolder) { // NOTE: This method runs on the main thread, but never blocks -- the blocking part of the work is // done by yielding the UnityWebRequest, which releases the main thread for other tasks while we // are waiting for the web request to complete (by the miracle of coroutines). // Let the caller create the UnityWebRequest, configuring it as they want. The caller can set the URL, // method, headers, anything they want. The only thing they can't do is call Send(), as we're in charge // of doing that. UnityWebRequest webRequest = request.creationCallback(); PtDebug.LogVerboseFormat("Web request: {0} {1}", webRequest.method, webRequest.url); bool cacheAllowed = cache != null && webRequest.method == "GET" && request.maxAgeMillis != CACHE_NONE; // Check the cache (if it's a GET request and cache is enabled). if (cacheAllowed) { bool cacheHit = false; byte[] cacheData = null; bool cacheReadDone = false; cache.RequestRead(webRequest.url, request.maxAgeMillis, (bool success, byte[] data) => { cacheHit = success; cacheData = data; cacheReadDone = true; }); while (!cacheReadDone) { yield return(null); } if (cacheHit) { PtDebug.LogVerboseFormat("Web request CACHE HIT: {0}, response: {1} bytes", webRequest.url, cacheData.Length); request.completionCallback(PolyStatus.Success(), /* responseCode */ 200, cacheData); // Return the buffer to the pool for reuse. CleanUpAfterWebRequest(bufferHolder); yield break; } else { PtDebug.LogVerboseFormat("Web request CACHE MISS: {0}.", webRequest.url); } } DownloadHandlerBuffer handler = new DownloadHandlerBuffer(); webRequest.downloadHandler = handler; // We need to asset that we actually succeeded in setting the download handler, because this can fail // if, for example, the creation callback mistakenly called Send(). PolyUtils.AssertTrue(webRequest.downloadHandler == handler, "Couldn't set download handler. It's either disposed of, or the creation callback mistakenly called Send()."); // Start the web request. This will suspend this coroutine until the request is done. PtDebug.LogVerboseFormat("Sending web request: {0}", webRequest.url); yield return(UnityCompat.SendWebRequest(webRequest)); // Request is finished. Call user-supplied callback. PtDebug.LogVerboseFormat("Web request finished: {0}, HTTP response code {1}, response: {2}", webRequest.url, webRequest.responseCode, webRequest.downloadHandler.text); PolyStatus status = UnityCompat.IsNetworkError(webRequest) ? PolyStatus.Error(webRequest.error) : PolyStatus.Success(); request.completionCallback(status, (int)webRequest.responseCode, webRequest.downloadHandler.data); // Cache the result, if applicable. if (!UnityCompat.IsNetworkError(webRequest) && cacheAllowed) { byte[] data = webRequest.downloadHandler.data; if (data != null && data.Length > 0) { byte[] copy = new byte[data.Length]; Buffer.BlockCopy(data, 0, copy, 0, data.Length); cache.RequestWrite(webRequest.url, copy); } } // Clean up. webRequest.Dispose(); CleanUpAfterWebRequest(bufferHolder); }
public static void ConfigureTestCases() { // The default application name is different in different versions of Unity. Set the value // in the beginning of the test to ensure all AndroidManifest.xml are using the same // application name across different versions of Unity. UnityCompat.SetApplicationId(UnityEditor.BuildTarget.Android, "com.Company.ProductName"); // Set of files to ignore (relative to the Assets/Plugins/Android directory) in all tests // that do not use the Gradle template. var nonGradleTemplateFilesToIgnore = new HashSet <string>() { Path.GetFileName(GRADLE_TEMPLATE_DISABLED), Path.GetFileName(GRADLE_TEMPLATE_LIBRARY_DISABLED) }; UnityEngine.Debug.Log("Setting up test cases for execution."); IntegrationTester.Runner.ScheduleTestCases(new [] { // This *must* be the first test case as other test cases depend upon it. new IntegrationTester.TestCase { Name = "ValidateAndroidTargetSelected", Method = ValidateAndroidTargetSelected, }, new IntegrationTester.TestCase { Name = "SetupDependencies", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); SetupDependencies(); var testCaseResult = new IntegrationTester.TestCaseResult(testCase); ValidateDependencies(testCaseResult); testCaseComplete(testCaseResult); } }, new IntegrationTester.TestCase { Name = "ResolveForGradleBuildSystemWithTemplate", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); SetupDependencies(); ResolveWithGradleTemplate( GRADLE_TEMPLATE_DISABLED, "ExpectedArtifacts/NoExport/GradleTemplate", testCase, testCaseComplete, otherExpectedFiles: new [] { "Assets/GeneratedLocalRepo/Firebase/m2repository/com/google/" + "firebase/firebase-app-unity/5.1.1/firebase-app-unity-5.1.1.aar" }, filesToIgnore: new HashSet <string> { Path.GetFileName(GRADLE_TEMPLATE_LIBRARY_DISABLED) }); } }, new IntegrationTester.TestCase { Name = "ResolverForGradleBuildSystemWithTemplateUsingJetifier", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); SetupDependencies(); GooglePlayServices.SettingsDialog.UseJetifier = true; ResolveWithGradleTemplate( GRADLE_TEMPLATE_DISABLED, "ExpectedArtifacts/NoExport/GradleTemplateJetifier", testCase, testCaseComplete, otherExpectedFiles: new [] { "Assets/GeneratedLocalRepo/Firebase/m2repository/com/google/" + "firebase/firebase-app-unity/5.1.1/firebase-app-unity-5.1.1.aar" }, filesToIgnore: new HashSet <string> { Path.GetFileName(GRADLE_TEMPLATE_LIBRARY_DISABLED) }); } }, new IntegrationTester.TestCase { Name = "ResolveForGradleBuildSystemLibraryWithTemplate", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); SetupDependencies(); ResolveWithGradleTemplate( GRADLE_TEMPLATE_LIBRARY_DISABLED, "ExpectedArtifacts/NoExport/GradleTemplateLibrary", testCase, testCaseComplete, otherExpectedFiles: new [] { "Assets/GeneratedLocalRepo/Firebase/m2repository/com/google/" + "firebase/firebase-app-unity/5.1.1/firebase-app-unity-5.1.1.aar" }, filesToIgnore: new HashSet <string> { Path.GetFileName(GRADLE_TEMPLATE_DISABLED) }); } }, new IntegrationTester.TestCase { Name = "ResolveForGradleBuildSystemWithTemplateEmpty", Method = (testCase, testCaseComplete) => { string enabledDependencies = "Assets/ExternalDependencyManager/Editor/TestDependencies.xml"; string disabledDependencies = "Assets/ExternalDependencyManager/Editor/TestDependenciesDISABLED.xml"; Action enableDependencies = () => { UnityEditor.AssetDatabase.MoveAsset(disabledDependencies, enabledDependencies); }; try { // Disable all XML dependencies. var error = UnityEditor.AssetDatabase.MoveAsset(enabledDependencies, disabledDependencies); if (!String.IsNullOrEmpty(error)) { testCaseComplete(new IntegrationTester.TestCaseResult(testCase) { ErrorMessages = new List <string>() { error } }); return; } ClearAllDependencies(); ResolveWithGradleTemplate( GRADLE_TEMPLATE_DISABLED, "ExpectedArtifacts/NoExport/GradleTemplateEmpty", testCase, (testCaseResult) => { enableDependencies(); testCaseComplete(testCaseResult); }, filesToIgnore: new HashSet <string> { Path.GetFileName(GRADLE_TEMPLATE_LIBRARY_DISABLED) }); } finally { enableDependencies(); } } }, new IntegrationTester.TestCase { Name = "ResolveForGradleBuildSystem", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); SetupDependencies(); Resolve("Gradle", false, "ExpectedArtifacts/NoExport/Gradle", null, nonGradleTemplateFilesToIgnore, testCase, testCaseComplete); } }, new IntegrationTester.TestCase { Name = "ResolveForGradleBuildSystemSync", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); SetupDependencies(); Resolve("Gradle", false, "ExpectedArtifacts/NoExport/Gradle", null, nonGradleTemplateFilesToIgnore, testCase, testCaseComplete, synchronous: true); } }, new IntegrationTester.TestCase { Name = "ResolveForGradleBuildSystemAndExport", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); SetupDependencies(); Resolve("Gradle", true, "ExpectedArtifacts/Export/Gradle", null, nonGradleTemplateFilesToIgnore, testCase, testCaseComplete); } }, new IntegrationTester.TestCase { Name = "ResolveAddedDependencies", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); SetupDependencies(); UpdateAdditionalDependenciesFile(true); Resolve("Gradle", true, "ExpectedArtifacts/Export/GradleAddedDeps", null, nonGradleTemplateFilesToIgnore, testCase, testCaseComplete); } }, new IntegrationTester.TestCase { Name = "ResolveRemovedDependencies", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); SetupDependencies(); // Add the additional dependencies file then immediately remove it. UpdateAdditionalDependenciesFile(true); UpdateAdditionalDependenciesFile(false); Resolve("Gradle", true, "ExpectedArtifacts/Export/Gradle", null, nonGradleTemplateFilesToIgnore, testCase, testCaseComplete); } }, new IntegrationTester.TestCase { Name = "DeleteResolvedLibraries", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); SetupDependencies(); Resolve("Gradle", true, "ExpectedArtifacts/Export/Gradle", null, nonGradleTemplateFilesToIgnore, testCase, (testCaseResult) => { PlayServicesResolver.DeleteResolvedLibrariesSync(); var unexpectedFilesMessage = new List <string>(); var resolvedFiles = ListFiles("Assets/Plugins/Android", nonGradleTemplateFilesToIgnore); if (resolvedFiles.Count > 0) { unexpectedFilesMessage.Add("Libraries not deleted!"); foreach (var filename in resolvedFiles.Values) { unexpectedFilesMessage.Add(filename); } } testCaseResult.ErrorMessages.AddRange(unexpectedFilesMessage); testCaseComplete(testCaseResult); }, synchronous: true); } }, new IntegrationTester.TestCase { Name = "ResolveForGradleBuildSystemWithTemplateDeleteLibraries", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); SetupDependencies(); var filesToIgnore = new HashSet <string> { Path.GetFileName(GRADLE_TEMPLATE_LIBRARY_DISABLED) }; ResolveWithGradleTemplate( GRADLE_TEMPLATE_DISABLED, "ExpectedArtifacts/NoExport/GradleTemplate", testCase, (testCaseResult) => { PlayServicesResolver.DeleteResolvedLibrariesSync(); testCaseResult.ErrorMessages.AddRange(CompareDirectoryContents( "ExpectedArtifacts/NoExport/GradleTemplateEmpty", "Assets/Plugins/Android", filesToIgnore)); if (File.Exists(GRADLE_TEMPLATE_ENABLED)) { File.Delete(GRADLE_TEMPLATE_ENABLED); } testCaseComplete(testCaseResult); }, deleteGradleTemplate: false, filesToIgnore: filesToIgnore); } }, }); // Internal build system for Android is removed in Unity 2019, even // UnityEditor.AndroidBuildSystem.Internal still exist. if (IntegrationTester.Runner.UnityVersion < 2019.0f) { IntegrationTester.Runner.ScheduleTestCases(new [] { new IntegrationTester.TestCase { Name = "ResolveForInternalBuildSystem", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); SetupDependencies(); Resolve("Internal", false, AarsWithNativeLibrariesSupported ? "ExpectedArtifacts/NoExport/InternalNativeAars" : "ExpectedArtifacts/NoExport/InternalNativeAarsExploded", null, nonGradleTemplateFilesToIgnore, testCase, testCaseComplete); } }, new IntegrationTester.TestCase { Name = "ResolveForInternalBuildSystemUsingJetifier", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); SetupDependencies(); GooglePlayServices.SettingsDialog.UseJetifier = true; Resolve("Internal", false, AarsWithNativeLibrariesSupported ? "ExpectedArtifacts/NoExport/InternalNativeAarsJetifier" : "ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier", null, nonGradleTemplateFilesToIgnore, testCase, testCaseComplete); } }, }); } // Test resolution with Android ABI filtering. if (IntegrationTester.Runner.UnityVersion >= 2018.0f) { IntegrationTester.Runner.ScheduleTestCase( new IntegrationTester.TestCase { Name = "ResolverForGradleBuildSystemUsingAbisArmeabiv7aAndArm64", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); Resolve("Gradle", false, "ExpectedArtifacts/NoExport/GradleArmeabiv7aArm64", "armeabi-v7a, arm64-v8a", nonGradleTemplateFilesToIgnore, testCase, testCaseComplete); } }); } else if (IntegrationTester.Runner.UnityVersion >= 5.0f) { IntegrationTester.Runner.ScheduleTestCase( new IntegrationTester.TestCase { Name = "ResolverForGradleBuildSystemUsingAbisArmeabiv7a", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); Resolve("Gradle", false, "ExpectedArtifacts/NoExport/GradleArmeabiv7a", "armeabi-v7a", nonGradleTemplateFilesToIgnore, testCase, testCaseComplete); } }); } }