using System; using System.Collections.Generic; using UnityEditor; using UnityEngine; using UnityEngine.Networking; namespace Unity.Services.Mediation.Dashboard.Editor { static class DashboardClient { const string k_ErrorProjectNotConfigured = "To access the full suite of Mediation Editor tools, please link your project to a Unity Dashboard project under Edit > Project Settings > Services"; const string k_ErrorFailedToRetrieveAdUnits = "Failed to retrieve Ad Units from the Dashboard; "; const string k_ErrorFailedToRetrieveGameId = "Failed to retrieve Game IDs from the Dashboard; "; const string k_ErrorFailedToRetrieveAdNetworks = "Failed to retrieve Configured ad Networks from the Dashboard; "; const string k_AdUnitsListUrl = "https://services.unity.com/api/monetize/mediation/v1/organizations/{0}/projects/{1}/ad-units"; const string k_GameIdUrl = "https://services.unity.com/api/monetize/mediation/v1/organizations/{0}/projects/{1}/app-adnetwork-parameters/{2}"; const string k_AdNetworksListUrl = "https://services.unity.com/api/monetize/mediation/v1/organizations/{0}/ad-networks"; const string k_AdNetworkUnity = "UNITY"; static DashboardPoller s_ProjectSettingsPoller; static DashboardPoller s_NoTimeoutPoller; static DashboardClient() { s_ProjectSettingsPoller = new DashboardPoller(IsCloudProjectSettingsValid); s_NoTimeoutPoller = new DashboardPoller(IsCloudProjectSettingsValid, 1.0, Int32.MaxValue); } static bool IsCloudProjectSettingsValid() { return !string.IsNullOrEmpty(CloudProjectSettings.projectId) && !string.IsNullOrEmpty(CloudProjectSettings.userId) && !string.IsNullOrEmpty(CloudProjectSettings.accessToken) && !string.IsNullOrEmpty(Core.Editor.OrganizationHandler.OrganizationProvider.Organization.Key); } /// /// Validates that the Cloud project settings are properly set so we can access the Dashboard /// /// True if we have the proper info to access the dashboard static void IsProjectReadyForConfigurationRequests(Action callback) { var isCloudProjectSettingsValid = IsCloudProjectSettingsValid(); if (isCloudProjectSettingsValid || s_ProjectSettingsPoller.FinishedPolling) { callback?.Invoke(isCloudProjectSettingsValid); } else { s_ProjectSettingsPoller.AddCallback(callback); } } /// /// Retrieves the request token and sets it in the unity web request's header /// /// The web request to fill /// Callback when the token is set static void PrepareRequestToken(UnityWebRequest request, Action onRequestReady) { request.downloadHandler = new DownloadHandlerBuffer(); request.SetRequestHeader("Content-Type", "application/json"); JwtToken.GetTokenAsync(token => { request.SetRequestHeader("Authorization", $"Bearer {token}"); onRequestReady?.Invoke(); }); } /// /// Returns an array of available Ad Units asynchronously /// /// Called upon completion, with an array of ad units or null if we failed. internal static void GetAdUnitsAsync(Action callback) { IsProjectReadyForConfigurationRequests(ready => { if (ready) { var orgId = Core.Editor.OrganizationHandler.OrganizationProvider.Organization.Key; var dashboardUri = new Uri(string.Format(k_AdUnitsListUrl, orgId, CloudProjectSettings.projectId)); var dashboardRequest = new UnityWebRequest(dashboardUri, UnityWebRequest.kHttpVerbGET); PrepareRequestToken(dashboardRequest, () => { var requestAsyncOperation = dashboardRequest.SendWebRequest(); requestAsyncOperation.completed += _ => OnAdUnitsInfoReady(dashboardRequest, callback); }); } else { MediationLogger.LogWarning(k_ErrorProjectNotConfigured); callback?.Invoke(null); return; } }); } internal static void OnAdUnitsInfoReady(UnityWebRequest request, Action callback) { #if UNITY_2020_1_OR_NEWER var isSuccess = request.result == UnityWebRequest.Result.Success; #else var isSuccess = !request.isNetworkError && !request.isHttpError; #endif if (!isSuccess) { MediationLogger.LogWarning($"{k_ErrorFailedToRetrieveAdUnits} {request.error}"); callback?.Invoke(null); return; } var adUnitInfos = JsonUtilityExtension.FromJsonArray(request.downloadHandler.text); callback?.Invoke(adUnitInfos); } /// /// Returns the game Id, asynchronously /// /// Called upon completion, with a dictionary of platform:gameId or null if we failed internal static void GetGameIdAsync(Action> callback) { IsProjectReadyForConfigurationRequests(ready => { if (ready) { var orgId = Core.Editor.OrganizationHandler.OrganizationProvider.Organization.Key; var dashboardUri = new Uri(string.Format(k_GameIdUrl, orgId, CloudProjectSettings.projectId, k_AdNetworkUnity)); var dashboardRequest = new UnityWebRequest(dashboardUri, UnityWebRequest.kHttpVerbGET); PrepareRequestToken(dashboardRequest, () => { var requestAsyncOperation = dashboardRequest.SendWebRequest(); requestAsyncOperation.completed += _ => OnGameIdReady(dashboardRequest, callback); }); } else { MediationLogger.LogWarning(k_ErrorProjectNotConfigured); callback?.Invoke(null); return; } }); } internal static void GetGameIdAsyncOrWait(Action> callback) { if (!IsCloudProjectSettingsValid()) { s_NoTimeoutPoller.AddCallback((success) => GetGameIdAsync(callback)); } else { GetGameIdAsync(callback); } } static void OnGameIdReady(UnityWebRequest request, Action> callback) { #if UNITY_2020_1_OR_NEWER var isSuccess = request.result == UnityWebRequest.Result.Success; #else var isSuccess = !request.isNetworkError && !request.isHttpError; #endif if (!isSuccess) { //Here a 404 means the ad network is not associated with a waterfall, so the id is not available. if (request.responseCode != 404) { MediationLogger.LogWarning($"{k_ErrorFailedToRetrieveGameId} {request.error}"); } callback?.Invoke(null); return; } var projectInfo = JsonUtilityExtension.FromJsonArray(request.downloadHandler.text); var gameIdPerPlatform = new Dictionary(); foreach (var project in projectInfo) { gameIdPerPlatform.Add(project.Platform, project.Params.GameId); } callback?.Invoke(gameIdPerPlatform); } /// /// Returns the list of configured and enabled ad networks, asynchronously /// /// Called upon completion, with an array of ad networks or null if we failed internal static void GetConfiguredAdNetworksAsync(Action> callback) { IsProjectReadyForConfigurationRequests(ready => { if (ready) { var orgId = Core.Editor.OrganizationHandler.OrganizationProvider.Organization.Key; var dashboardUri = new Uri(string.Format(k_AdNetworksListUrl, orgId)); var dashboardRequest = new UnityWebRequest(dashboardUri, UnityWebRequest.kHttpVerbGET); PrepareRequestToken(dashboardRequest, () => { var requestAsyncOperation = dashboardRequest.SendWebRequest(); requestAsyncOperation.completed += _ => OnConfiguredAdNetworksReady(dashboardRequest, callback); }); } else { MediationLogger.LogWarning(k_ErrorProjectNotConfigured); callback?.Invoke(null); } }); } internal static void GetConfiguredAdNetworksAsyncOrWait(Action> callback) { if (!IsCloudProjectSettingsValid()) { s_NoTimeoutPoller.AddCallback((success) => GetConfiguredAdNetworksAsync(callback)); } else { GetConfiguredAdNetworksAsync(callback); } } static void OnConfiguredAdNetworksReady(UnityWebRequest request, Action> callback) { #if UNITY_2020_1_OR_NEWER var isSuccess = request.result == UnityWebRequest.Result.Success; #else var isSuccess = !request.isNetworkError && !request.isHttpError; #endif if (!isSuccess) { MediationLogger.LogWarning($"{k_ErrorFailedToRetrieveAdNetworks} {request.error}"); callback?.Invoke(null); return; } AdNetworksInfoJson[] projectInfo = JsonUtilityExtension.FromJsonArray(request.downloadHandler.text); var activeAdNetworks = new List(); foreach (var network in projectInfo) { if (!network.IsDisabled) { activeAdNetworks.Add(network.name); } } callback?.Invoke(activeAdNetworks); } [System.Serializable] internal class OrgInfoJson { public string org_foreign_key; public string BackendId => org_foreign_key; } [System.Serializable] internal class AdNetworksInfoJson { public string coreOrganizationId; public string CoreOrganizationId => coreOrganizationId; public string name; public string Name => name; public bool isDisabled; public bool IsDisabled => isDisabled; public bool? isValidated; public bool? IsValidated => isValidated; public string validatedAt; public string ValidatedAt => validatedAt; } [System.Serializable] internal class ProjectInfoJson { [System.Serializable] public class ProjectInfoParamsJson { public string gameId; public string GameId => gameId; } public string CoreOrganizationId { get; protected set; } public string appId; public string AppId => appId; public string projectId; public string ProjectId => projectId; public string adNetwork; public string AdNetwork => adNetwork; public string platform; public string Platform => platform; public ProjectInfoParamsJson parameters; public ProjectInfoParamsJson Params => parameters; } [System.Serializable] internal class AdUnitInfoJson { public string adUnitId; public string AdUnitId => adUnitId; public string coreOrganization; public string Organization => coreOrganization; public string adFormat; public string AdFormat => adFormat; public string platform; public string Platform => platform; public string appId; public string AppId => appId; public string name; public string Name => name; public bool isArchived; public bool IsArchived => isArchived; } } }