using System; using System.Globalization; using System.Net.Http; using System.Net.Sockets; using System.Threading.Tasks; using JetBrains.Annotations; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using UnityEditor; using UnityEngine; using UnityEngine.SceneManagement; #if UNITY_2021_1_OR_NEWER using SystemInfo = UnityEngine.Device.SystemInfo; #else using SystemInfo = UnityEngine.SystemInfo; #endif namespace Needle.Engine { internal static class NeedleEngineAuthorization { private const int trialDuration = 15; internal static bool TrialEnded => DaysUntilTrialEnds < 0; internal static bool IsInTrialPeriod => DaysUntilTrialEnds >= 0; internal static int DaysUntilTrialEnds { get { if (_daysUntilTrialEnds != null) return _daysUntilTrialEnds.Value; _daysUntilTrialEnds = (GetFirstInstallation().AddDays(trialDuration) - DateTime.Now).Days; return _daysUntilTrialEnds.Value; } } #if UNITY_EDITOR private static DateTime _firstInstallationTime = default; [InitializeOnLoadMethod] private static void Init() { _firstInstallationTime = GetFirstInstallation(); _daysUntilTrialEnds = DaysUntilTrialEnds; } #endif private static int? _daysUntilTrialEnds; private static DateTime GetFirstInstallation() { try { #if !UNITY_EDITOR return DateTime.Now; #else if (_firstInstallationTime != default) return _firstInstallationTime; var now = DateTime.Now; var nowStr = now.ToString(CultureInfo.CurrentCulture); var firstInstallTimeStr = EditorPrefs.GetString("NEEDLE_ENGINE_first_installation_date", nowStr); if (firstInstallTimeStr == nowStr) { EditorPrefs.SetString("NEEDLE_ENGINE_first_installation_date", nowStr); return now; } // Looks like trial time was modified -> this is invalid if (string.IsNullOrWhiteSpace(firstInstallTimeStr)) { } if (DateTime.TryParse(firstInstallTimeStr, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.AllowWhiteSpaces, out var firstInstallTime)) { return firstInstallTime; } if (DateTime.TryParse(firstInstallTimeStr, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AllowWhiteSpaces, out firstInstallTime)) { return firstInstallTime; } return DateTime.Now.Subtract(TimeSpan.FromDays(99999)); #endif } catch (Exception) { // ignore } return DateTime.Now; } private static HttpClient _client; private static HttpClient client => _client ??= new HttpClient() { Timeout = TimeSpan.FromSeconds(10) }; internal static async void OnBeforeExport() { #if UNITY_EDITOR var scene = SceneManager.GetActiveScene(); if (scene.IsValid()) { await Authentication.EnsureServerIsRunning(); var user = await Authentication.GetUserInformation(); if (user == null) { return; } var url = Authentication.localServerUrl + "/api/projects"; var guid = AssetDatabase.AssetPathToGUID(scene.path); var body = new JObject(); body["project_id"] = guid; body["project_name"] = scene.name; var bodyStr = body.ToString(); await client.PostAsync(url, new StringContent(bodyStr, System.Text.Encoding.UTF8, "application/json")); } #else await Task.CompletedTask; #endif } internal struct UserProjectIsValidResponse { public bool license_satisfied; /// /// How many other users have access to this project /// public int other_user_count; /// /// Is the user in the project /// public bool user_is_in_project; public bool all_users_have_team_license; [CanBeNull] public string code; [CanBeNull] public string message; [CanBeNull] public string reason; public bool treat_as_error; public bool display_message_to_user; } #pragma warning disable CS0414 private static bool loginInProgress = false; #pragma warning restore CS0414 private static DateTime lastLoginClickedTime; internal static bool OnGUI(out bool invalidLicense) { #if !UNITY_EDITOR invalidLicense = false; return false; #else var didDrawGUI = false; invalidLicense = false; #pragma warning disable CS4014 IsAuthorized(false); #pragma warning restore CS4014 if (_response.success == false && _response.response != null) { var res = _response.response.Value; if (res.code == "missing_team_license") {; if (res.treat_as_error) { didDrawGUI = true; invalidLicense = true; EditorGUILayout.HelpBox(res.message, MessageType.Warning); EditorGUILayout.HelpBox(res.reason, MessageType.None); } } } else if (!Authentication.IsLoggedIn()) { didDrawGUI = true; invalidLicense = true; using (new GUILayout.HorizontalScope()) { var checksAreRunning = Authentication.LoginCheckInProgress || Authentication.UserInfoCheckInProgress; if (Authentication.LoginCheckInProgress) { EditorGUILayout.HelpBox("Checking login status...", MessageType.None); } else if (Authentication.UserInfoCheckInProgress) { EditorGUILayout.HelpBox("Getting user information...", MessageType.None); } else if (!loginInProgress) { var msg = Authentication._userInformationError; if (string.IsNullOrWhiteSpace(msg)) { msg = "Please log in to start using Needle Engine."; } EditorGUILayout.HelpBox(msg, MessageType.Warning); } else { EditorGUILayout.HelpBox("Logging in... Please complete the login in the browser.", MessageType.Info); } if (!checksAreRunning) { async void RunAction() { loginInProgress = true; // If the user just recently clicked the login and is still not logged in try to logout // Maybe he's already logged in to another account but login fails because the new account is // Logged in on too many machines... if(DateTime.Now - lastLoginClickedTime < TimeSpan.FromSeconds(60)) { await Authentication.Logout(); } lastLoginClickedTime = DateTime.Now; await Authentication.LoginAsync(true, false); loginInProgress = false; } if (GUILayout.Button("Login", GUILayout.Height(38), GUILayout.Width(60))) { RunAction(); } } } } return didDrawGUI; #endif } private static AuthorizedResponse _response = default; private static DateTime _lastAuthorizedCheckTime = DateTime.MinValue; internal struct AuthorizedResponse { public bool success; public string message; public UserProjectIsValidResponse? response; } internal static async Task IsAuthorized(bool force) { if (!force) { var checkThreshold = _response.success ? 30 : 5; if (DateTime.Now - _lastAuthorizedCheckTime < TimeSpan.FromSeconds(checkThreshold)) { return _response; } } _lastAuthorizedCheckTime = DateTime.Now; #if UNITY_EDITOR var scene = SceneManager.GetActiveScene(); if (scene.IsValid()) { var loggedIn = Authentication.IsLoggedIn(); if (!loggedIn) { await Authentication.EnsureServerIsRunning(); loggedIn = Authentication.IsLoggedIn(); } if (loggedIn == false) { _response.success = false; _response.response = null; _response.message = "Please log in to your Needle account"; return _response; } var guid = AssetDatabase.AssetPathToGUID(scene.path); var url = $"{Authentication.localServerUrl}/api/projects/{guid}?org={Authentication.SelectedTeam}"; try { var res = await client.GetAsync(url); if (res.IsSuccessStatusCode) { var content = await res.Content.ReadAsStringAsync(); if (content.StartsWith("{")) { var json = JsonConvert.DeserializeObject(content); if (json.license_satisfied) { _response.success = true; _response.response = json; return _response; } if (json.treat_as_error) _response.success = false; else _response.success = true; _response.message = json.message + "\n" + json.reason; _response.response = json; return _response; } } } catch (SocketException) { // Ignore } _response.success = false; _response.message = "Failed to authorize project. Please make sure you have the correct permissions and are logged in to your Needle account"; _response.response = null; return _response; } #else await Task.CompletedTask; #endif _response.success = true; return _response; } } }