Files
2025-11-30 08:35:03 +02:00

311 lines
8.5 KiB
C#

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;
/// <summary>
/// How many other users have access to this project
/// </summary>
public int other_user_count;
/// <summary>
/// Is the user in the project
/// </summary>
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<AuthorizedResponse> 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<UserProjectIsValidResponse>(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;
}
}
}