From 5800e9fed427d18e1a0fb19dde41adb5211ed50c Mon Sep 17 00:00:00 2001 From: tung_tung Date: Tue, 3 Feb 2026 18:06:19 +0700 Subject: [PATCH 1/4] COR-5984: implement authservice --- Src/Authorizer.cs | 4 +- Src/BCIGameItf.cs | 30 -- Src/CortexClient.cs | 38 +- Src/EmotivUnityItf.cs | 176 +------- Src/Runtime/Models/CortexErrorCode.cs | 13 + Src/Runtime/SDK/Auth/AuthService.cs | 581 ++++++++++++++++++++++++++ Src/Runtime/SDK/Auth/IAuthService.cs | 51 +++ Src/Runtime/SDK/EmotivFactory.cs | 15 + Src/Types.cs | 4 + 9 files changed, 702 insertions(+), 210 deletions(-) create mode 100644 Src/Runtime/Models/CortexErrorCode.cs create mode 100644 Src/Runtime/SDK/Auth/AuthService.cs create mode 100644 Src/Runtime/SDK/Auth/IAuthService.cs create mode 100644 Src/Runtime/SDK/EmotivFactory.cs diff --git a/Src/Authorizer.cs b/Src/Authorizer.cs index 0bd48da..ae893ad 100644 --- a/Src/Authorizer.cs +++ b/Src/Authorizer.cs @@ -222,7 +222,7 @@ private void OnRefreshTokenOK(object sender, string cortexToken) SaveToken(tokenInfo); // get license information - _ctxClient.GetLicenseInfo(cortexToken); + _ctxClient.GetLicenseInfo(); } private void OnWSConnectDone(object sender, bool isConnected) @@ -271,7 +271,7 @@ private void OnAuthorizedOK(object sender, string cortexToken) Authorizer.SaveToken(tokenInfo); // get license information - _ctxClient.GetLicenseInfo(cortexToken); + _ctxClient.GetLicenseInfo(); } else { AuthorizedFailed(this, cortexToken); UnityEngine.Debug.Log("Invalid Token."); diff --git a/Src/BCIGameItf.cs b/Src/BCIGameItf.cs index 40a4847..510ded7 100644 --- a/Src/BCIGameItf.cs +++ b/Src/BCIGameItf.cs @@ -41,11 +41,6 @@ public void LoginWithAuthenticationCode(string code) emotivUnityItf.LoginWithAuthenticationCode(code); } - public void AcceptEulaAndPrivacyPolicy() - { - emotivUnityItf.AcceptEulaAndPrivacyPolicy(); - } - /// /// Get detected headsets. Returns a list of detected headsets. /// @@ -173,15 +168,6 @@ public async Task ProcessCallback(string args) { } #endif - #if USE_EMBEDDED_LIB || UNITY_ANDROID || UNITY_IOS - /// - /// Authenticate with Emotiv. It will open system browser to login and get the authentication code. - /// - public async Task AuthenticateAsync() - { - await emotivUnityItf.AuthenticateAsync(); - } - #endif /// /// Initialize and start the application. It should be called when the app has granted permissions: bluetooth, location, write external storage. @@ -390,12 +376,6 @@ public void EraseDataForMCTrainingAction(string action = DEFAULT_MC_ACTION) emotivUnityItf.EraseMCTraining(action); } - // logout - public void Logout() - { - emotivUnityItf.Logout(); - } - /// /// Query the dates having consumer data within a specified date range. /// @@ -414,16 +394,6 @@ public void QueryDayDetailOfConsumerData(DateTime date) { emotivUnityItf.QueryDayDetailOfConsumerData(date); } - public void OpenURL(string url) - { - emotivUnityItf.OpenURL(url); - } - - public bool IsWebViewOpened() - { - return emotivUnityItf.IsWebViewOpened; - } - public string LoadedProfilePlayer() { return emotivUnityItf.LoadedProfileName; } diff --git a/Src/CortexClient.cs b/Src/CortexClient.cs index 32412e5..9d25510 100644 --- a/Src/CortexClient.cs +++ b/Src/CortexClient.cs @@ -26,6 +26,7 @@ using System; using System.Threading; using Newtonsoft.Json.Linq; +using Emotiv.Cortex.Models; using System.Collections.Generic; using System.Collections; @@ -61,11 +62,13 @@ public abstract class CortexClient public event EventHandler AccessRightGrantedDone; public event EventHandler AuthorizeOK; public event EventHandler GetUserLoginDone; + public event EventHandler LoginDone; public event EventHandler EULAAccepted; public event EventHandler EULANotAccepted; // return cortexToken if user has not accept eula to proceed next step public event EventHandler UserLoginNotify; public event EventHandler UserLogoutNotify; public event EventHandler GetLicenseInfoDone; + public event EventHandler<(CortexErrorCode error, License data)> GetLicenseInfoResult; public event EventHandler CreateSessionOK; public event EventHandler UpdateSessionOK; public event EventHandler SubscribeDataDone; @@ -103,6 +106,7 @@ public abstract class CortexClient public event EventHandler> QueryDayDetailOfConsumerDataDone; public event EventHandler ExportRecordsFinished; public event EventHandler DataPostProcessingFinished; + public string CurrentCortexToken { get; internal set; } = string.Empty; public virtual void Init(object context = null) {} @@ -185,6 +189,11 @@ public void OnMessageReceived(string receievedMsg) UnityEngine.Debug.Log("An error received: " + messageError); //Send Error message event ErrorMsgReceived(this, new ErrorMsgEventArgs(code, messageError, method)); + + if (method == "getLicenseInfo") + { + GetLicenseInfoResult?.Invoke(this, (CortexErrorCode.UnknownError, null)); + } } else { // handle response @@ -302,11 +311,12 @@ private void HandleResponse(string method, JToken data) loginData.EmotivId = data["username"].ToString(); String message = data["message"].ToString(); UnityEngine.Debug.Log("login message: " + message); - GetUserLoginDone(this, loginData); + LoginDone(this, loginData); } else if (method == "logout") { String message = data["message"].ToString(); + CurrentCortexToken = string.Empty; UserLogoutNotify(this, message); // get user login info GetUserLogin(); @@ -324,12 +334,14 @@ private void HandleResponse(string method, JToken data) else if (method == "generateNewToken") { string cortexToken = data["cortexToken"].ToString(); + CurrentCortexToken = cortexToken; RefreshTokenOK(this, cortexToken); } else if (method == "getLicenseInfo") { License lic = new License(data["license"]); - GetLicenseInfoDone(this, lic); + // GetLicenseInfoDone(this, lic); + GetLicenseInfoResult?.Invoke(this, (CortexErrorCode.OK, lic)); } else if (method == "getUserInformation") { @@ -338,6 +350,7 @@ private void HandleResponse(string method, JToken data) else if (method == "authorize") { string token = (string)data["cortexToken"]; + CurrentCortexToken = token; if (data["warning"] != null) { JObject warning = (JObject)data["warning"]; @@ -658,6 +671,16 @@ public void LoginWithAuthenticationCode(string code) } // accept eula + public void AcceptEulaAndPrivacyPolicy() + { + if (string.IsNullOrEmpty(CurrentCortexToken)) + { + UnityEngine.Debug.LogWarning("AcceptEulaAndPrivacyPolicy requested but no cortex token is available."); + return; + } + AcceptEulaAndPrivacyPolicy(CurrentCortexToken); + } + public void AcceptEulaAndPrivacyPolicy(string cortexToken) { JObject param = new JObject( @@ -667,10 +690,17 @@ public void AcceptEulaAndPrivacyPolicy(string cortexToken) } // get license information - public void GetLicenseInfo(string cortexToken) + public void GetLicenseInfo() { + if (string.IsNullOrEmpty(CurrentCortexToken)) + { + UnityEngine.Debug.LogWarning("GetLicenseInfo requested but no cortex token is available."); + GetLicenseInfoResult?.Invoke(this, (CortexErrorCode.UnknownError, null)); + return; + } + JObject param = new JObject( - new JProperty("cortexToken", cortexToken) + new JProperty("cortexToken", CurrentCortexToken) ); SendTextMessage(param, "getLicenseInfo", true); } diff --git a/Src/EmotivUnityItf.cs b/Src/EmotivUnityItf.cs index 3b1b805..6771f9c 100644 --- a/Src/EmotivUnityItf.cs +++ b/Src/EmotivUnityItf.cs @@ -7,16 +7,6 @@ using System.Threading.Tasks; using UnityEngine; -#if USE_EMBEDDED_LIB || UNITY_ANDROID || UNITY_IOS -using Cdm.Authentication.Browser; -using Cdm.Authentication.OAuth2; -using System.Threading; -using System.Security.Cryptography; -using System.Text; -using Cdm.Authentication.Clients; -using Newtonsoft.Json; -#endif - namespace EmotivUnityPlugin { @@ -70,7 +60,6 @@ public class EmotivUnityItf private string _messageLog = ""; - private bool _isWebViewOpened = false; private List _mentalCommandActionSensitivity = new List(); // trained signature actions @@ -102,7 +91,6 @@ public class EmotivUnityItf public List MentalCommandActionSensitivity { get => _mentalCommandActionSensitivity; set => _mentalCommandActionSensitivity = value; } public List DatesHavingConsumerData { get => _datesHavingConsumerData; set => _datesHavingConsumerData = value; } public List MentalStateDatas { get => _mentalStateDatas; set => _mentalStateDatas = value; } - public bool IsWebViewOpened { get => _isWebViewOpened; set => _isWebViewOpened = value; } public string LoadedProfileName { get => _loadedProfileName; set => _loadedProfileName = value; } public string WorkingHeadsetId { get => _workingHeadsetId; set => _workingHeadsetId = value; } public Record RecentRecord { get => _recentRecord; set => _recentRecord = value; } @@ -121,15 +109,6 @@ public string GetCurrentEmotivId() } -#if USE_EMBEDDED_LIB || UNITY_ANDROID || UNITY_IOS - private CrossPlatformBrowser _crossPlatformBrowser; - private AuthenticationSession _authenticationSession; - private CancellationTokenSource _cancellationTokenSource; - - private static readonly char[] HEX_ARRAY = "0123456789abcdef".ToCharArray(); - - #endif - #if USE_EMBEDDED_LIB && UNITY_STANDALONE_WIN && !UNITY_EDITOR public async Task ProcessCallback(string args) { @@ -205,7 +184,7 @@ public ConnectToCortexStates GetConnectToCortexState() /// The path to Emotiv Launcher file path (optional). Only for desktop version and work with Cortex Service. public void Init(string clientId, string clientSecret, string appName, bool allowSaveLogToFile = true, bool isDataBufferUsing = true, - string appUrl = "", string providerName = "", string emotivAppsPath = "") + string appUrl = "wss://localhost:6868", string providerName = "", string emotivAppsPath = "") { if (string.IsNullOrEmpty(clientId) || string.IsNullOrEmpty(clientSecret)) { @@ -225,11 +204,6 @@ public void Init(string clientId, string clientSecret, string appName, // init logger MyLogger.Instance.Init(appName, allowSaveLogToFile); - // init authentication for Android and Embedded Cortex Desktop - #if UNITY_ANDROID || USE_EMBEDDED_LIB || UNITY_IOS - InitForAuthentication(clientId, clientSecret); - #endif - _dsManager.IsDataBufferUsing = isDataBufferUsing; // init bcitraining _bciTraining.Init(); @@ -302,10 +276,6 @@ public void Start(object context = null) /// public void Stop() { - #if USE_EMBEDDED_LIB - _cancellationTokenSource?.Cancel(); - _authenticationSession?.Dispose(); - #endif #if UNITY_ANDROID || UNITY_IOS UniWebViewManager.Instance?.Cleanup(); #endif @@ -1076,7 +1046,7 @@ private void OnMentalCommandReceived(object sender, MentalCommandEventArgs data) { string dataText = "com data: " + data.Act + ", power: " + data.Pow.ToString() + ", time " + data.Time.ToString(); // print out data to console - UnityEngine.Debug.Log(dataText); + //UnityEngine.Debug.Log(dataText); LatestMentalCommand.act = data.Act; LatestMentalCommand.pow = data.Pow; } @@ -1285,147 +1255,5 @@ public void ResetProfileAndTrainingData() _desiredErasingProfiles.Clear(); } - public void OpenURL(string url) - { - #if UNITY_ANDROID || UNITY_IOS - _isWebViewOpened = true; - UniWebViewManager.Instance.OpenURL( - url, - onClosed: (isClosed) => { - Debug.Log($"UniWebView closed! isClosed: {isClosed}"); - _isWebViewOpened = false; - - } - ); - #else - Application.OpenURL(url); - #endif - } - - #if USE_EMBEDDED_LIB || UNITY_ANDROID || UNITY_IOS - private string BytesToHex(byte[] bytes) - { - char[] hexChars = new char[bytes.Length * 2]; - for (int j = 0; j < bytes.Length; ++j) - { - int v = bytes[j] & 0xFF; - hexChars[j * 2] = HEX_ARRAY[v >> 4]; - hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; - } - return new string(hexChars); - } - - private string Md5(string s) - { - try - { - using (var md5 = MD5.Create()) - { - byte[] inputBytes = Encoding.UTF8.GetBytes(s); - byte[] hashBytes = md5.ComputeHash(inputBytes); - return BytesToHex(hashBytes); - } - } - catch (Exception e) - { - Debug.LogError(e); - return string.Empty; - } - } - - private void InitForAuthentication(string clientId, string clientSecret) - { - string server = ""; - #if DEV_SERVER - UnityEngine.Debug.Log("Development build detected. Using development server."); - server = "cerebrum-dev.emotivcloud.com"; -#else - UnityEngine.Debug.Log("Production build detected. Using production server."); - server = "cerebrum.emotivcloud.com"; -#endif - string hash = Md5(clientId); - string prefixRedirectUrl = "emotiv-" + hash; - string redirectUrl = prefixRedirectUrl + "://authorize"; - string serverUrl = $"https://{server}"; - #if UNITY_ANDROID || UNITY_IOS - string authorizationUrl = $"https://{server}/api/oauth/authorize/?response_type=code" + - $"&client_id={Uri.EscapeDataString(clientId)}" + - $"&redirect_uri={redirectUrl}" + $"&hide_signup=1&hide_social_signin=1"; - UniWebViewManager.Instance.Init( - authorizationUrl, - prefixRedirectUrl - ); - #else - _crossPlatformBrowser = new CrossPlatformBrowser(); - _crossPlatformBrowser.platformBrowsers.Add(RuntimePlatform.WindowsEditor, new WindowsSystemBrowser()); - _crossPlatformBrowser.platformBrowsers.Add(RuntimePlatform.WindowsPlayer, new WindowsSystemBrowser()); - _crossPlatformBrowser.platformBrowsers.Add(RuntimePlatform.OSXEditor, new DeepLinkBrowser()); - _crossPlatformBrowser.platformBrowsers.Add(RuntimePlatform.OSXPlayer, new DeepLinkBrowser()); - -#if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN - // Deep linking is not supported on Windows (except UWP), so RegistryConfig is used to handle this case. - new RegistryConfig(prefixRedirectUrl).Configure(); -#endif - - var configuration = new AuthorizationCodeFlow.Configuration() - { - clientId = clientId, - clientSecret = clientSecret, - redirectUri = redirectUrl, - scope = "" - }; - var auth = new MockServerAuth(configuration, serverUrl); - _authenticationSession = new AuthenticationSession(auth, _crossPlatformBrowser); - _authenticationSession.loginTimeout = TimeSpan.FromSeconds(600); - #endif - } - public async Task AuthenticateAsync() - { - #if UNITY_ANDROID || UNITY_IOS - _isWebViewOpened = true; - UniWebViewManager.Instance.StartAuthorization( - onSuccess: (authCode) => { - Debug.Log($"UniWebView Authorization succeeded! Starting login with auth code"); - LoginWithAuthenticationCode(authCode); - _isWebViewOpened = false; - }, - onError: (errorCode, errorMessage) => { - Debug.LogError($"Authorization failed! Error {errorCode}: {errorMessage}"); - _isWebViewOpened = false; - - } - ); - #else - if (_authenticationSession != null) - { - _cancellationTokenSource?.Dispose(); - _cancellationTokenSource = new CancellationTokenSource(); - try - { - MessageLog = "Starting authentication..."; - var accessTokenResponse = - await _authenticationSession.AuthenticateAsync(_cancellationTokenSource.Token); - - LoginWithAuthenticationCode(accessTokenResponse.accessToken); - } - catch (AuthorizationCodeRequestException ex) - { - Debug.LogError($"{nameof(AuthorizationCodeRequestException)} " + - $"error: {ex.error.code}, description: {ex.error.description}, uri: {ex.error.uri}"); - } - catch (AccessTokenRequestException ex) - { - Debug.LogError($"{nameof(AccessTokenRequestException)} " + - $"error: {ex.error.code}, description: {ex.error.description}, uri: {ex.error.uri}"); - } - catch (Exception ex) - { - Debug.LogError( "Exception " + ex.Message); - } - } - #endif - } - #endif - } } diff --git a/Src/Runtime/Models/CortexErrorCode.cs b/Src/Runtime/Models/CortexErrorCode.cs new file mode 100644 index 0000000..9915724 --- /dev/null +++ b/Src/Runtime/Models/CortexErrorCode.cs @@ -0,0 +1,13 @@ +namespace Emotiv.Cortex.Models +{ + public enum CortexErrorCode + { + OK = 0, + PermissionsDenied = 2, + WebSocketConnectFailed = 3, + AccessRightDenied = 4, + AuthorizationFailed = 5, + LicenseExpiredOrInvalid = 6, + UnknownError + } +} diff --git a/Src/Runtime/SDK/Auth/AuthService.cs b/Src/Runtime/SDK/Auth/AuthService.cs new file mode 100644 index 0000000..bc140cc --- /dev/null +++ b/Src/Runtime/SDK/Auth/AuthService.cs @@ -0,0 +1,581 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Emotiv.Cortex.Models; +using EmotivUnityPlugin; +using UnityEngine; +#if USE_EMBEDDED_LIB || UNITY_ANDROID || UNITY_IOS +using Cdm.Authentication.Browser; +using Cdm.Authentication.OAuth2; +using System.Threading; +using System.Security.Cryptography; +using System.Text; +using Cdm.Authentication.Clients; +#endif +#if UNITY_ANDROID +using UnityEngine.Android; +#endif + +namespace Emotiv.Cortex.SDK.Auth +{ + public class AuthService : IAuthService + { + public static AuthService Instance { get; } = new AuthService(); + + private readonly CortexClient _client; + private UserDataInfo _loggedInUser = new UserDataInfo(); + private bool _isInitialized; + +#if USE_EMBEDDED_LIB || UNITY_ANDROID || UNITY_IOS + private CrossPlatformBrowser _crossPlatformBrowser; + private AuthenticationSession _authenticationSession; + private CancellationTokenSource _cancellationTokenSource; + private static readonly char[] HEX_ARRAY = "0123456789abcdef".ToCharArray(); +#endif + + public AuthService() + { + _client = CortexClient.Instance; + } + + internal AuthService(CortexClient client) + { + _client = client ?? throw new ArgumentNullException(nameof(client)); + } + + public async Task<(CortexErrorCode Code, UserDataInfo User)> InitAndAuthorizeAsync() + { + if (!await EnsureAndroidPermissionsAsync()) + { + return (CortexErrorCode.PermissionsDenied, new UserDataInfo()); + } + + InitConfigFromAppConfig(); + +#if UNITY_ANDROID || UNITY_IOS || USE_EMBEDDED_LIB + InitAuthentication(AppConfig.ClientId, AppConfig.ClientSecret); +#endif + + object context = GetAndroidContext(); + _client.Init(context); + _client.Open(); + + bool isConnected = await WaitForWsConnectAsync(); + if (!isConnected) + { + return (CortexErrorCode.WebSocketConnectFailed, new UserDataInfo()); + } + _isInitialized = true; + UnityEngine.Debug.Log("AuthService: InitAndAuthorizeAsync(): WS connected."); + + UserDataInfo loginData = await WaitForGetUserLoginAsync(); + return await CompleteAuthorizationAsync(loginData); + } + + public async Task<(CortexErrorCode Code, UserDataInfo User)> LoginAndAuthorizeAsync() + { + UnityEngine.Debug.Log("AuthService: LoginAndAuthorizeAsync(): Start login flow"); + return await AuthenticateAndAuthorizeAsync(); + } + + public void AcceptEulaAndPrivacyPolicy() + { + _client.AcceptEulaAndPrivacyPolicy(); + } + + public void OpenPrivacyPolicy() + { + string url = "https://id.emotiv.com/eoidc/privacy/privacy_policy/"; + OpenURL(url); + } + + public void OpenSignup() + { +#if DEV_SERVER + string url = "https://id-dev.emotiv.com/account/registration/"; +#else + string url = "https://id.emotiv.com/eoidc/account/registration/"; +#endif + OpenURL(url); + } + + public void OpenDeleteAccount() + { + string url = "https://account.emotiv.com/my-account/delete/"; + OpenURL(url); + } + + public void Logout() + { + if (string.IsNullOrEmpty(_loggedInUser.EmotivId)) + { + Debug.LogWarning("Logout requested but no user is logged in."); + return; + } + _client.Logout(_loggedInUser.EmotivId); + _loggedInUser = new UserDataInfo(); + } + + private void OpenURL(string url) + { +#if UNITY_ANDROID || UNITY_IOS + UniWebViewManager.Instance.OpenURL( + url, + onClosed: (isClosed) => { + Debug.Log($"UniWebView closed! isClosed: {isClosed}"); + } + ); +#else + Application.OpenURL(url); +#endif + } + + private async Task<(CortexErrorCode Code, UserDataInfo User)> LoginWithAuthenticationCodeAsync(string code) + { + UnityEngine.Debug.Log("AuthService: LoginWithAuthenticationCodeAsync(): code: " + code); + UserDataInfo loginData = await WaitForLoginAsync(code); + return await CompleteAuthorizationAsync(loginData); + } + + private async Task<(CortexErrorCode Code, UserDataInfo User)> AuthenticateAndAuthorizeAsync() + { + return await AuthenticateAsync(); + } + + private async Task<(CortexErrorCode Code, UserDataInfo User)> AuthenticateAsync() + { +#if UNITY_ANDROID || UNITY_IOS + var tcs = new TaskCompletionSource<(CortexErrorCode Code, UserDataInfo User)>(); + UniWebViewManager.Instance.StartAuthorization( + onSuccess: async (authCode) => { + Debug.Log("UniWebView Authorization succeeded! Starting login with auth code"); + var result = await LoginWithAuthenticationCodeAsync(authCode); + tcs.TrySetResult(result); + }, + onError: (errorCode, errorMessage) => { + Debug.LogError($"Authorization failed! Error {errorCode}: {errorMessage}"); + tcs.TrySetResult((CortexErrorCode.AuthorizationFailed, new UserDataInfo())); + } + ); + return await tcs.Task; +#elif USE_EMBEDDED_LIB + if (_authenticationSession != null) + { + _cancellationTokenSource?.Dispose(); + _cancellationTokenSource = new CancellationTokenSource(); + try + { + var accessTokenResponse = + await _authenticationSession.AuthenticateAsync(_cancellationTokenSource.Token); + + return await LoginWithAuthenticationCodeAsync(accessTokenResponse.accessToken); + } + catch (AuthorizationCodeRequestException ex) + { + Debug.LogError($"{nameof(AuthorizationCodeRequestException)} " + + $"error: {ex.error.code}, description: {ex.error.description}, uri: {ex.error.uri}"); + } + catch (AccessTokenRequestException ex) + { + Debug.LogError($"{nameof(AccessTokenRequestException)} " + + $"error: {ex.error.code}, description: {ex.error.description}, uri: {ex.error.uri}"); + } + catch (Exception ex) + { + Debug.LogError("Exception " + ex.Message); + } + } + return (CortexErrorCode.UnknownError, new UserDataInfo()); +#else + await Task.Yield(); + return (CortexErrorCode.UnknownError, new UserDataInfo()); +#endif + } + + private async Task<(CortexErrorCode Code, UserDataInfo User)> CompleteAuthorizationAsync(UserDataInfo loginData) + { + if (string.IsNullOrEmpty(loginData.EmotivId)) + { + return (CortexErrorCode.OK, loginData); + } + + _loggedInUser = loginData; + + UnityEngine.Debug.Log("AuthService: CompleteAuthorizationAsync(): User logged in: " + loginData.EmotivId); + +#if UNITY_ANDROID || UNITY_IOS || USE_EMBEDDED_LIB + bool hasAccessRight = true; +#else + bool hasAccessRight = await CheckAccessRightsAsync(); + if (!hasAccessRight) + { + _client.RequestAccess(); + return (CortexErrorCode.AccessRightDenied, loginData); + } +#endif + UnityEngine.Debug.Log("AuthService: CompleteAuthorizationAsync(): Access rights granted."); + var authorizeResult = await AuthorizeAsync(); + if (!authorizeResult.Success) + { + return (CortexErrorCode.AuthorizationFailed, loginData); + } + + UnityEngine.Debug.Log("AuthService: CompleteAuthorizationAsync(): Authorized."); + + License license = await GetLicenseInfoAsync(); + UnityEngine.Debug.Log("AuthService: CompleteAuthorizationAsync(): License info retrieved." + + (license != null ? $" expired: {license.expired}" : " license is null")); + if (license == null || license.expired) + { + return (CortexErrorCode.LicenseExpiredOrInvalid, loginData); + } + + var resultUser = new UserDataInfo(loginData.LastLoginTime, authorizeResult.CortexToken, loginData.EmotivId); + _loggedInUser = resultUser; + return (CortexErrorCode.OK, resultUser); + } + + private void InitConfigFromAppConfig() + { + string appUrl = "wss://localhost:6868"; +#if !USE_EMBEDDED_LIB && !UNITY_ANDROID && !UNITY_IOS + if (!string.IsNullOrEmpty(AppConfig.AppUrl)) + { + appUrl = AppConfig.AppUrl; + } +#endif + + Config.Init( + AppConfig.ClientId, + AppConfig.ClientSecret, + AppConfig.AppName, + AppConfig.AllowSaveLogToFile, + appUrl, + "", + "" + ); + + MyLogger.Instance.Init(AppConfig.AppName, AppConfig.AllowSaveLogToFile); + } + + private object GetAndroidContext() + { +#if UNITY_ANDROID + AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); + AndroidJavaObject currentActivity = unityPlayer.GetStatic("currentActivity"); + return currentActivity; +#else + return null; +#endif + } + + private Task WaitForWsConnectAsync() + { + var tcs = new TaskCompletionSource(); + EventHandler handler = null; + handler = (sender, isConnected) => + { + _client.WSConnectDone -= handler; + tcs.TrySetResult(isConnected); + }; + _client.WSConnectDone += handler; + return tcs.Task; + } + + private Task WaitForGetUserLoginAsync() + { + var tcs = new TaskCompletionSource(); + EventHandler handler = null; + handler = (sender, data) => + { + _client.GetUserLoginDone -= handler; + UnityEngine.Debug.Log("AuthService: WaitForGetUserLoginAsync(): User logged in: " + data.EmotivId); + if (!string.IsNullOrEmpty(data.EmotivId)) + { + _loggedInUser = data; + } + tcs.TrySetResult(data); + }; + _client.GetUserLoginDone += handler; + _client.GetUserLogin(); + return tcs.Task; + } + + private Task WaitForLoginAsync(string authCode) + { + var tcs = new TaskCompletionSource(); + EventHandler handler = null; + handler = (sender, data) => + { + _client.LoginDone -= handler; + UnityEngine.Debug.Log("AuthService: WaitForLoginAsync(): User logged in: " + data.EmotivId); + if (!string.IsNullOrEmpty(data.EmotivId)) + { + _loggedInUser = data; + } + tcs.TrySetResult(data); + }; + _client.LoginDone += handler; + _client.LoginWithAuthenticationCode(authCode); + return tcs.Task; + } + + private Task CheckAccessRightsAsync() + { + var tcs = new TaskCompletionSource(); + + EventHandler okHandler = null; + EventHandler errorHandler = null; + + okHandler = (sender, hasAccessRight) => + { + _client.HasAccessRightOK -= okHandler; + _client.ErrorMsgReceived -= errorHandler; + tcs.TrySetResult(hasAccessRight); + }; + + errorHandler = (sender, error) => + { + if (error.MethodName != "hasAccessRight") + { + return; + } + _client.HasAccessRightOK -= okHandler; + _client.ErrorMsgReceived -= errorHandler; + tcs.TrySetResult(false); + }; + + _client.HasAccessRightOK += okHandler; + _client.ErrorMsgReceived += errorHandler; + _client.HasAccessRights(); + return tcs.Task; + } + + private Task<(bool Success, string CortexToken)> AuthorizeAsync() + { + var tcs = new TaskCompletionSource<(bool Success, string CortexToken)>(); + + EventHandler okHandler = null; + EventHandler errorHandler = null; + EventHandler eulaHandler = null; + + okHandler = (sender, token) => + { + Cleanup(); + tcs.TrySetResult((true, token)); + }; + + eulaHandler = (sender, token) => + { + Cleanup(); + tcs.TrySetResult((false, token)); + }; + + errorHandler = (sender, error) => + { + if (error.MethodName != "authorize") + { + return; + } + Cleanup(); + tcs.TrySetResult((false, "")); + }; + + void Cleanup() + { + _client.AuthorizeOK -= okHandler; + _client.EULANotAccepted -= eulaHandler; + _client.ErrorMsgReceived -= errorHandler; + } + + _client.AuthorizeOK += okHandler; + _client.EULANotAccepted += eulaHandler; + _client.ErrorMsgReceived += errorHandler; + + _client.Authorize(string.Empty, 5000); + return tcs.Task; + } + + private Task GetLicenseInfoAsync() + { + var tcs = new TaskCompletionSource(); + EventHandler<(CortexErrorCode error, License data)> handler = null; + + handler = (sender, result) => + { + _client.GetLicenseInfoResult -= handler; + UnityEngine.Debug.Log("AuthService: GetLicenseInfoAsync(): result code: " + result.error); + if (result.error == CortexErrorCode.OK) + { + tcs.TrySetResult(result.data); + } + else + { + tcs.TrySetResult(null); + } + }; + + _client.GetLicenseInfoResult += handler; + + _client.GetLicenseInfo(); + return tcs.Task; + } + + private async Task EnsureAndroidPermissionsAsync() + { +#if UNITY_ANDROID + if (HasAllPermissions()) + { + return true; + } + + foreach (var permission in GetRequiredPermissions()) + { + if (!Permission.HasUserAuthorizedPermission(permission)) + { + Permission.RequestUserPermission(permission); + while (!Permission.HasUserAuthorizedPermission(permission)) + { + await Task.Delay(100); + } + } + } + + return HasAllPermissions(); +#else + await Task.Yield(); + return true; +#endif + } + +#if UNITY_ANDROID + private static string[] GetRequiredPermissions() + { + if (GetAndroidVersion() >= 31) + { + return new[] { + "android.permission.ACCESS_FINE_LOCATION", + "android.permission.BLUETOOTH_SCAN", + "android.permission.BLUETOOTH_CONNECT" + }; + } + + return new[] { + "android.permission.ACCESS_FINE_LOCATION", + "android.permission.BLUETOOTH" + }; + } + + private static bool HasAllPermissions() + { + foreach (var permission in GetRequiredPermissions()) + { + if (!Permission.HasUserAuthorizedPermission(permission)) + { + return false; + } + } + return true; + } + + private static int GetAndroidVersion() + { + string osInfo = SystemInfo.operatingSystem; + if (osInfo.Contains("Android")) + { + int apiIndex = osInfo.IndexOf("API-"); + if (apiIndex != -1) + { + string apiLevel = osInfo.Substring(apiIndex + 4).Split(' ')[0]; + if (int.TryParse(apiLevel, out int androidVersion)) + { + return androidVersion; + } + } + } + return 0; + } +#endif + + private void InitAuthentication(string clientId, string clientSecret) + { +#if USE_EMBEDDED_LIB || UNITY_ANDROID || UNITY_IOS + string server = ""; +#if DEV_SERVER + Debug.Log("Development build detected. Using development server."); + server = "cerebrum-dev.emotivcloud.com"; +#else + Debug.Log("Production build detected. Using production server."); + server = "cerebrum.emotivcloud.com"; +#endif + string hash = Md5(clientId); + string prefixRedirectUrl = "emotiv-" + hash; + string redirectUrl = prefixRedirectUrl + "://authorize"; + string serverUrl = $"https://{server}"; +#if UNITY_ANDROID || UNITY_IOS + string authorizationUrl = $"https://{server}/api/oauth/authorize/?response_type=code" + + $"&client_id={Uri.EscapeDataString(clientId)}" + + $"&redirect_uri={redirectUrl}" + "&hide_signup=1&hide_social_signin=1"; + UniWebViewManager.Instance.Init( + authorizationUrl, + prefixRedirectUrl + ); +#else + _crossPlatformBrowser = new CrossPlatformBrowser(); + _crossPlatformBrowser.platformBrowsers.Add(RuntimePlatform.WindowsEditor, new WindowsSystemBrowser()); + _crossPlatformBrowser.platformBrowsers.Add(RuntimePlatform.WindowsPlayer, new WindowsSystemBrowser()); + _crossPlatformBrowser.platformBrowsers.Add(RuntimePlatform.OSXEditor, new DeepLinkBrowser()); + _crossPlatformBrowser.platformBrowsers.Add(RuntimePlatform.OSXPlayer, new DeepLinkBrowser()); + +#if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN + // Deep linking is not supported on Windows (except UWP), so RegistryConfig is used to handle this case. + new RegistryConfig(prefixRedirectUrl).Configure(); +#endif + + var configuration = new AuthorizationCodeFlow.Configuration() + { + clientId = clientId, + clientSecret = clientSecret, + redirectUri = redirectUrl, + scope = "" + }; + var auth = new MockServerAuth(configuration, serverUrl); + _authenticationSession = new AuthenticationSession(auth, _crossPlatformBrowser); + _authenticationSession.loginTimeout = TimeSpan.FromSeconds(600); +#endif +#endif + } + +#if USE_EMBEDDED_LIB || UNITY_ANDROID || UNITY_IOS + private string BytesToHex(byte[] bytes) + { + char[] hexChars = new char[bytes.Length * 2]; + for (int j = 0; j < bytes.Length; ++j) + { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = HEX_ARRAY[v >> 4]; + hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; + } + return new string(hexChars); + } + + private string Md5(string s) + { + try + { + using (var md5 = MD5.Create()) + { + byte[] inputBytes = Encoding.UTF8.GetBytes(s); + byte[] hashBytes = md5.ComputeHash(inputBytes); + return BytesToHex(hashBytes); + } + } + catch (Exception e) + { + Debug.LogError(e); + return string.Empty; + } + } +#endif + } +} diff --git a/Src/Runtime/SDK/Auth/IAuthService.cs b/Src/Runtime/SDK/Auth/IAuthService.cs new file mode 100644 index 0000000..b8a4e70 --- /dev/null +++ b/Src/Runtime/SDK/Auth/IAuthService.cs @@ -0,0 +1,51 @@ +using System.Threading.Tasks; +using Emotiv.Cortex.Models; +using EmotivUnityPlugin; // TODO (Tung Nguyen): remove this line when move all old code to new SDK + +namespace Emotiv.Cortex.SDK.Auth +{ + public interface IAuthService + { + + /// + /// Initializes the SDK and attempts authorization using an already logged-in user. + /// + /// + /// A tuple containing the and the resulting . + /// + Task<(CortexErrorCode Code, UserDataInfo User)> InitAndAuthorizeAsync(); + + /// + /// Starts the interactive login flow and attempts authorization. + /// + /// + /// A tuple containing the and the resulting . + /// + Task<(CortexErrorCode Code, UserDataInfo User)> LoginAndAuthorizeAsync(); + + /// + /// Accepts the EULA and privacy policy for the current user. + /// + void AcceptEulaAndPrivacyPolicy(); + + /// + /// Opens the privacy policy page. + /// + void OpenPrivacyPolicy(); + + /// + /// Opens the sign-up page. + /// + void OpenSignup(); + + /// + /// Opens the delete-account page. + /// + void OpenDeleteAccount(); + + /// + /// Logs out the current user and clears local auth state. + /// + void Logout(); + } +} \ No newline at end of file diff --git a/Src/Runtime/SDK/EmotivFactory.cs b/Src/Runtime/SDK/EmotivFactory.cs new file mode 100644 index 0000000..48acd47 --- /dev/null +++ b/Src/Runtime/SDK/EmotivFactory.cs @@ -0,0 +1,15 @@ +namespace Emotiv.Cortex.SDK +{ + public static class EmotivFactory + { + public static Auth.IAuthService GetAuthService() + { + return Auth.AuthService.Instance; + } + + public static Auth.IAuthService getAuthService() + { + return Auth.AuthService.Instance; + } + } +} diff --git a/Src/Types.cs b/Src/Types.cs index 314c356..0f900ed 100644 --- a/Src/Types.cs +++ b/Src/Types.cs @@ -381,6 +381,10 @@ public UserDataInfo(double time = 0, string token = "", string emotivId = "") { public double LastLoginTime { get; set; } public string CortexToken { get; set;} public string EmotivId { get; set;} + public bool IsAuthorized() + { + return !string.IsNullOrEmpty(CortexToken) && !string.IsNullOrEmpty(EmotivId); + } public UserDataInfo(SerializationInfo info, StreamingContext ctxt) { LastLoginTime = (double)info.GetValue("lastLoginTime", typeof(double)); From 340976e5f0e1a72c7ece7ec353798b7cb2e152c9 Mon Sep 17 00:00:00 2001 From: tung_tung Date: Wed, 4 Feb 2026 12:30:47 +0700 Subject: [PATCH 2/4] update rework --- Src/Runtime/SDK/Auth/AuthService.cs | 102 +++++++++++++--------------- 1 file changed, 46 insertions(+), 56 deletions(-) diff --git a/Src/Runtime/SDK/Auth/AuthService.cs b/Src/Runtime/SDK/Auth/AuthService.cs index bc140cc..b698cdf 100644 --- a/Src/Runtime/SDK/Auth/AuthService.cs +++ b/Src/Runtime/SDK/Auth/AuthService.cs @@ -75,7 +75,52 @@ internal AuthService(CortexClient client) public async Task<(CortexErrorCode Code, UserDataInfo User)> LoginAndAuthorizeAsync() { UnityEngine.Debug.Log("AuthService: LoginAndAuthorizeAsync(): Start login flow"); - return await AuthenticateAndAuthorizeAsync(); +#if UNITY_ANDROID || UNITY_IOS + var tcs = new TaskCompletionSource<(CortexErrorCode Code, UserDataInfo User)>(); + UniWebViewManager.Instance.StartAuthorization( + onSuccess: async (authCode) => { + Debug.Log("UniWebView Authorization succeeded! Starting login with auth code"); + var result = await LoginWithAuthenticationCodeAsync(authCode); + tcs.TrySetResult(result); + }, + onError: (errorCode, errorMessage) => { + Debug.LogError($"Authorization failed! Error {errorCode}: {errorMessage}"); + tcs.TrySetResult((CortexErrorCode.AuthorizationFailed, new UserDataInfo())); + } + ); + return await tcs.Task; +#elif USE_EMBEDDED_LIB + if (_authenticationSession != null) + { + _cancellationTokenSource?.Dispose(); + _cancellationTokenSource = new CancellationTokenSource(); + try + { + var accessTokenResponse = + await _authenticationSession.AuthenticateAsync(_cancellationTokenSource.Token); + + return await LoginWithAuthenticationCodeAsync(accessTokenResponse.accessToken); + } + catch (AuthorizationCodeRequestException ex) + { + Debug.LogError($"{nameof(AuthorizationCodeRequestException)} " + + $"error: {ex.error.code}, description: {ex.error.description}, uri: {ex.error.uri}"); + } + catch (AccessTokenRequestException ex) + { + Debug.LogError($"{nameof(AccessTokenRequestException)} " + + $"error: {ex.error.code}, description: {ex.error.description}, uri: {ex.error.uri}"); + } + catch (Exception ex) + { + Debug.LogError("Exception " + ex.Message); + } + } + return (CortexErrorCode.UnknownError, new UserDataInfo()); +#else + await Task.Yield(); + return (CortexErrorCode.UnknownError, new UserDataInfo()); +#endif } public void AcceptEulaAndPrivacyPolicy() @@ -137,61 +182,6 @@ private void OpenURL(string url) return await CompleteAuthorizationAsync(loginData); } - private async Task<(CortexErrorCode Code, UserDataInfo User)> AuthenticateAndAuthorizeAsync() - { - return await AuthenticateAsync(); - } - - private async Task<(CortexErrorCode Code, UserDataInfo User)> AuthenticateAsync() - { -#if UNITY_ANDROID || UNITY_IOS - var tcs = new TaskCompletionSource<(CortexErrorCode Code, UserDataInfo User)>(); - UniWebViewManager.Instance.StartAuthorization( - onSuccess: async (authCode) => { - Debug.Log("UniWebView Authorization succeeded! Starting login with auth code"); - var result = await LoginWithAuthenticationCodeAsync(authCode); - tcs.TrySetResult(result); - }, - onError: (errorCode, errorMessage) => { - Debug.LogError($"Authorization failed! Error {errorCode}: {errorMessage}"); - tcs.TrySetResult((CortexErrorCode.AuthorizationFailed, new UserDataInfo())); - } - ); - return await tcs.Task; -#elif USE_EMBEDDED_LIB - if (_authenticationSession != null) - { - _cancellationTokenSource?.Dispose(); - _cancellationTokenSource = new CancellationTokenSource(); - try - { - var accessTokenResponse = - await _authenticationSession.AuthenticateAsync(_cancellationTokenSource.Token); - - return await LoginWithAuthenticationCodeAsync(accessTokenResponse.accessToken); - } - catch (AuthorizationCodeRequestException ex) - { - Debug.LogError($"{nameof(AuthorizationCodeRequestException)} " + - $"error: {ex.error.code}, description: {ex.error.description}, uri: {ex.error.uri}"); - } - catch (AccessTokenRequestException ex) - { - Debug.LogError($"{nameof(AccessTokenRequestException)} " + - $"error: {ex.error.code}, description: {ex.error.description}, uri: {ex.error.uri}"); - } - catch (Exception ex) - { - Debug.LogError("Exception " + ex.Message); - } - } - return (CortexErrorCode.UnknownError, new UserDataInfo()); -#else - await Task.Yield(); - return (CortexErrorCode.UnknownError, new UserDataInfo()); -#endif - } - private async Task<(CortexErrorCode Code, UserDataInfo User)> CompleteAuthorizationAsync(UserDataInfo loginData) { if (string.IsNullOrEmpty(loginData.EmotivId)) From 9443667a2604c0abc899c088378633ab0e689b0c Mon Sep 17 00:00:00 2001 From: tung_tung Date: Wed, 4 Feb 2026 16:31:23 +0700 Subject: [PATCH 3/4] update rework rename factory and namespace --- Src/Authorizer.cs | 4 +- Src/CortexClient.cs | 6 +-- Src/EmbeddedCortexClient.cs | 6 +-- Src/Runtime/Models/CortexErrorCode.cs | 10 ++--- Src/Runtime/SDK/Auth/AuthService.cs | 57 ++++----------------------- Src/Runtime/SDK/Auth/IAuthService.cs | 22 +---------- Src/Runtime/SDK/EmotivFactory.cs | 15 ------- Src/Runtime/SDK/ServiceFactory.cs | 10 +++++ Src/WebsocketCortexClient.cs | 6 +-- 9 files changed, 35 insertions(+), 101 deletions(-) delete mode 100644 Src/Runtime/SDK/EmotivFactory.cs create mode 100644 Src/Runtime/SDK/ServiceFactory.cs diff --git a/Src/Authorizer.cs b/Src/Authorizer.cs index ae893ad..3c7c893 100644 --- a/Src/Authorizer.cs +++ b/Src/Authorizer.cs @@ -65,7 +65,7 @@ public string CurrentEmotivId public Authorizer() { - _ctxClient.WSConnectDone += OnWSConnectDone; + _ctxClient.CortexConnectionStared += OnCortexConnectionStared; _ctxClient.GetUserLoginDone += OnGetUserLoginDone; _ctxClient.UserLoginNotify += OnUserLoginNotify; // inform user loggin _ctxClient.UserLogoutNotify += OnUserLogoutNotify; // inform user log out @@ -225,7 +225,7 @@ private void OnRefreshTokenOK(object sender, string cortexToken) _ctxClient.GetLicenseInfo(); } - private void OnWSConnectDone(object sender, bool isConnected) + private void OnCortexConnectionStared(object sender, bool isConnected) { if (isConnected) { #if UNITY_ANDROID || UNITY_IOS diff --git a/Src/CortexClient.cs b/Src/CortexClient.cs index 9d25510..1855dc8 100644 --- a/Src/CortexClient.cs +++ b/Src/CortexClient.cs @@ -50,7 +50,7 @@ public abstract class CortexClient public AutoResetEvent m_MessageReceiveEvent = new AutoResetEvent(false); public AutoResetEvent m_OpenedEvent = new AutoResetEvent(false); - public event EventHandler WSConnectDone; + public event EventHandler CortexConnectionStared; public event EventHandler ErrorMsgReceived; public event EventHandler StreamDataReceived; public event EventHandler> QueryHeadsetOK; @@ -152,9 +152,9 @@ protected string PrepareRequest(string method, JObject param, bool hasParam = tr return request.ToString(); } - public void OnWSConnected(bool isConnected) + public void OnCortexConnectionStared(bool isConnected) { - WSConnectDone(this, isConnected); + CortexConnectionStared(this, isConnected); } /// diff --git a/Src/EmbeddedCortexClient.cs b/Src/EmbeddedCortexClient.cs index ab1f0b7..c542479 100644 --- a/Src/EmbeddedCortexClient.cs +++ b/Src/EmbeddedCortexClient.cs @@ -37,7 +37,7 @@ public class CortexLibInterfaceProxy : AndroidJavaProxy { public CortexLibInterfaceProxy() : base("com.emotiv.unityplugin.CortexConnectionInterface") { } void onReceivedMessage(String msg) => EmbeddedCortexClient.Instance.OnMessageReceived(msg); - void onCortexStarted() { Debug.Log("Cortex Lib Started"); EmbeddedCortexClient.Instance.OnWSConnected(true); } + void onCortexStarted() { Debug.Log("Cortex Lib Started"); EmbeddedCortexClient.Instance.OnCortexConnectionStared(true); } } public class CortexLogHandler : AndroidJavaProxy { @@ -75,7 +75,7 @@ public class CortexIOSHandler [AOT.MonoPInvokeCallback(typeof(MessageCallback))] private static void OnMessageReceived(string message) => EmbeddedCortexClient.Instance.OnMessageReceived(message); [AOT.MonoPInvokeCallback(typeof(StartedCallback))] - private static void OnCortexLibIosStarted() { Debug.Log("OnCortexLibIosStarted"); EmbeddedCortexClient.Instance.OnWSConnected(true); } + private static void OnCortexLibIosStarted() { Debug.Log("OnCortexLibIosStarted"); EmbeddedCortexClient.Instance.OnCortexConnectionStared(true); } public static void RegisterCallback() { RegisterUnityResponseCallback(OnMessageReceived); RegisterUnityStartedCallback(OnCortexLibIosStarted); } } #endif @@ -124,7 +124,7 @@ public override void Init(object context = null) } private static void OnMessageReceivedStatic(string message) => EmbeddedCortexClient.Instance.OnMessageReceived(message); - private static void OnWSConnectedStatic() => EmbeddedCortexClient.Instance.OnWSConnected(true); + private static void OnCortexConnectionStaredStatic() => EmbeddedCortexClient.Instance.OnCortexConnectionStared(true); #if USE_EMBEDDED_LIB private void CortexStarted(object? sender, bool e) diff --git a/Src/Runtime/Models/CortexErrorCode.cs b/Src/Runtime/Models/CortexErrorCode.cs index 9915724..ac39fed 100644 --- a/Src/Runtime/Models/CortexErrorCode.cs +++ b/Src/Runtime/Models/CortexErrorCode.cs @@ -3,11 +3,11 @@ namespace Emotiv.Cortex.Models public enum CortexErrorCode { OK = 0, - PermissionsDenied = 2, - WebSocketConnectFailed = 3, - AccessRightDenied = 4, - AuthorizationFailed = 5, - LicenseExpiredOrInvalid = 6, + PermissionsDenied = 2, // Indicates that the requested operation failed because the required permissions were not granted. + CortexConnectionError = 3, // Indicates that the connection to Cortex started unsuccessfully. + NoEULAAccepted = 4, // Indicates that the user has not accepted the End User License Agreement (EULA). Only for Cortex v3. Must be removed at Cortex v5 + AuthorizationFailed = 5, // Indicates that the authorization process failed overall. + LicenseError = 6, // Indicates that there is an issue with the license (e.g., invalid or expired license). UnknownError } } diff --git a/Src/Runtime/SDK/Auth/AuthService.cs b/Src/Runtime/SDK/Auth/AuthService.cs index b698cdf..e07fc59 100644 --- a/Src/Runtime/SDK/Auth/AuthService.cs +++ b/Src/Runtime/SDK/Auth/AuthService.cs @@ -16,7 +16,7 @@ using UnityEngine.Android; #endif -namespace Emotiv.Cortex.SDK.Auth +namespace Emotiv.Cortex.Service { public class AuthService : IAuthService { @@ -60,10 +60,10 @@ internal AuthService(CortexClient client) _client.Init(context); _client.Open(); - bool isConnected = await WaitForWsConnectAsync(); + bool isConnected = await WaitForCortexConnectionStaredAsync(); if (!isConnected) { - return (CortexErrorCode.WebSocketConnectFailed, new UserDataInfo()); + return (CortexErrorCode.CortexConnectionError, new UserDataInfo()); } _isInitialized = true; UnityEngine.Debug.Log("AuthService: InitAndAuthorizeAsync(): WS connected."); @@ -123,33 +123,6 @@ internal AuthService(CortexClient client) #endif } - public void AcceptEulaAndPrivacyPolicy() - { - _client.AcceptEulaAndPrivacyPolicy(); - } - - public void OpenPrivacyPolicy() - { - string url = "https://id.emotiv.com/eoidc/privacy/privacy_policy/"; - OpenURL(url); - } - - public void OpenSignup() - { -#if DEV_SERVER - string url = "https://id-dev.emotiv.com/account/registration/"; -#else - string url = "https://id.emotiv.com/eoidc/account/registration/"; -#endif - OpenURL(url); - } - - public void OpenDeleteAccount() - { - string url = "https://account.emotiv.com/my-account/delete/"; - OpenURL(url); - } - public void Logout() { if (string.IsNullOrEmpty(_loggedInUser.EmotivId)) @@ -161,20 +134,6 @@ public void Logout() _loggedInUser = new UserDataInfo(); } - private void OpenURL(string url) - { -#if UNITY_ANDROID || UNITY_IOS - UniWebViewManager.Instance.OpenURL( - url, - onClosed: (isClosed) => { - Debug.Log($"UniWebView closed! isClosed: {isClosed}"); - } - ); -#else - Application.OpenURL(url); -#endif - } - private async Task<(CortexErrorCode Code, UserDataInfo User)> LoginWithAuthenticationCodeAsync(string code) { UnityEngine.Debug.Log("AuthService: LoginWithAuthenticationCodeAsync(): code: " + code); @@ -200,7 +159,7 @@ private void OpenURL(string url) if (!hasAccessRight) { _client.RequestAccess(); - return (CortexErrorCode.AccessRightDenied, loginData); + return (CortexErrorCode.NoEULAAccepted, loginData); } #endif UnityEngine.Debug.Log("AuthService: CompleteAuthorizationAsync(): Access rights granted."); @@ -217,7 +176,7 @@ private void OpenURL(string url) (license != null ? $" expired: {license.expired}" : " license is null")); if (license == null || license.expired) { - return (CortexErrorCode.LicenseExpiredOrInvalid, loginData); + return (CortexErrorCode.LicenseError, loginData); } var resultUser = new UserDataInfo(loginData.LastLoginTime, authorizeResult.CortexToken, loginData.EmotivId); @@ -259,16 +218,16 @@ private object GetAndroidContext() #endif } - private Task WaitForWsConnectAsync() + private Task WaitForCortexConnectionStaredAsync() { var tcs = new TaskCompletionSource(); EventHandler handler = null; handler = (sender, isConnected) => { - _client.WSConnectDone -= handler; + _client.CortexConnectionStared -= handler; tcs.TrySetResult(isConnected); }; - _client.WSConnectDone += handler; + _client.CortexConnectionStared += handler; return tcs.Task; } diff --git a/Src/Runtime/SDK/Auth/IAuthService.cs b/Src/Runtime/SDK/Auth/IAuthService.cs index b8a4e70..03aa822 100644 --- a/Src/Runtime/SDK/Auth/IAuthService.cs +++ b/Src/Runtime/SDK/Auth/IAuthService.cs @@ -2,7 +2,7 @@ using Emotiv.Cortex.Models; using EmotivUnityPlugin; // TODO (Tung Nguyen): remove this line when move all old code to new SDK -namespace Emotiv.Cortex.SDK.Auth +namespace Emotiv.Cortex.Service { public interface IAuthService { @@ -23,26 +23,6 @@ public interface IAuthService /// Task<(CortexErrorCode Code, UserDataInfo User)> LoginAndAuthorizeAsync(); - /// - /// Accepts the EULA and privacy policy for the current user. - /// - void AcceptEulaAndPrivacyPolicy(); - - /// - /// Opens the privacy policy page. - /// - void OpenPrivacyPolicy(); - - /// - /// Opens the sign-up page. - /// - void OpenSignup(); - - /// - /// Opens the delete-account page. - /// - void OpenDeleteAccount(); - /// /// Logs out the current user and clears local auth state. /// diff --git a/Src/Runtime/SDK/EmotivFactory.cs b/Src/Runtime/SDK/EmotivFactory.cs deleted file mode 100644 index 48acd47..0000000 --- a/Src/Runtime/SDK/EmotivFactory.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Emotiv.Cortex.SDK -{ - public static class EmotivFactory - { - public static Auth.IAuthService GetAuthService() - { - return Auth.AuthService.Instance; - } - - public static Auth.IAuthService getAuthService() - { - return Auth.AuthService.Instance; - } - } -} diff --git a/Src/Runtime/SDK/ServiceFactory.cs b/Src/Runtime/SDK/ServiceFactory.cs new file mode 100644 index 0000000..adf6bfb --- /dev/null +++ b/Src/Runtime/SDK/ServiceFactory.cs @@ -0,0 +1,10 @@ +namespace Emotiv.Cortex.Service +{ + public static class ServiceFactory + { + public static IAuthService GetAuthService() + { + return AuthService.Instance; + } + } +} diff --git a/Src/WebsocketCortexClient.cs b/Src/WebsocketCortexClient.cs index 9d4145f..778064e 100644 --- a/Src/WebsocketCortexClient.cs +++ b/Src/WebsocketCortexClient.cs @@ -152,7 +152,7 @@ private void WebSocketClient_MessageReceived(object sender, MessageReceivedEvent /// private void WebSocketClient_Closed(object sender, EventArgs e) { - OnWSConnected(false); + OnCortexConnectionStared(false); // start connecting cortex service again if (_wscTimer != null) _wscTimer.Start(); @@ -165,7 +165,7 @@ private void WebSocketClient_Opened(object sender, EventArgs e) { m_OpenedEvent.Set(); if (_wSC.State == WebSocketState.Open) { - OnWSConnected(true); + OnCortexConnectionStared(true); // stop timer _wscTimer.Stop(); @@ -183,7 +183,7 @@ private void WebSocketClient_Error(object sender, SuperSocket.ClientEngine.Error if (e.Exception.InnerException != null) { UnityEngine.Debug.Log(e.Exception.InnerException.GetType()); - OnWSConnected(false); + OnCortexConnectionStared(false); // start connecting cortex service again _wscTimer.Start(); } From 54e0a3d5dbf9dfbc923d7c9c7babaa4e36833ad9 Mon Sep 17 00:00:00 2001 From: tung_tung Date: Mon, 9 Feb 2026 09:43:33 +0700 Subject: [PATCH 4/4] COR-5984: update rework --- Src/Runtime/SDK/Auth/AuthService.cs | 8 ++++---- Src/Runtime/SDK/Auth/IAuthService.cs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Src/Runtime/SDK/Auth/AuthService.cs b/Src/Runtime/SDK/Auth/AuthService.cs index e07fc59..e87e6f4 100644 --- a/Src/Runtime/SDK/Auth/AuthService.cs +++ b/Src/Runtime/SDK/Auth/AuthService.cs @@ -43,7 +43,7 @@ internal AuthService(CortexClient client) _client = client ?? throw new ArgumentNullException(nameof(client)); } - public async Task<(CortexErrorCode Code, UserDataInfo User)> InitAndAuthorizeAsync() + public async Task<(CortexErrorCode Code, UserDataInfo User)> InitAsync() { if (!await EnsureAndroidPermissionsAsync()) { @@ -66,15 +66,15 @@ internal AuthService(CortexClient client) return (CortexErrorCode.CortexConnectionError, new UserDataInfo()); } _isInitialized = true; - UnityEngine.Debug.Log("AuthService: InitAndAuthorizeAsync(): WS connected."); + UnityEngine.Debug.Log("AuthService: InitAsync(): WS connected."); UserDataInfo loginData = await WaitForGetUserLoginAsync(); return await CompleteAuthorizationAsync(loginData); } - public async Task<(CortexErrorCode Code, UserDataInfo User)> LoginAndAuthorizeAsync() + public async Task<(CortexErrorCode Code, UserDataInfo User)> LoginAsync() { - UnityEngine.Debug.Log("AuthService: LoginAndAuthorizeAsync(): Start login flow"); + UnityEngine.Debug.Log("AuthService: LoginAsync(): Start login flow"); #if UNITY_ANDROID || UNITY_IOS var tcs = new TaskCompletionSource<(CortexErrorCode Code, UserDataInfo User)>(); UniWebViewManager.Instance.StartAuthorization( diff --git a/Src/Runtime/SDK/Auth/IAuthService.cs b/Src/Runtime/SDK/Auth/IAuthService.cs index 03aa822..958f9f2 100644 --- a/Src/Runtime/SDK/Auth/IAuthService.cs +++ b/Src/Runtime/SDK/Auth/IAuthService.cs @@ -13,7 +13,7 @@ public interface IAuthService /// /// A tuple containing the and the resulting . /// - Task<(CortexErrorCode Code, UserDataInfo User)> InitAndAuthorizeAsync(); + Task<(CortexErrorCode Code, UserDataInfo User)> InitAsync(); /// /// Starts the interactive login flow and attempts authorization. @@ -21,7 +21,7 @@ public interface IAuthService /// /// A tuple containing the and the resulting . /// - Task<(CortexErrorCode Code, UserDataInfo User)> LoginAndAuthorizeAsync(); + Task<(CortexErrorCode Code, UserDataInfo User)> LoginAsync(); /// /// Logs out the current user and clears local auth state.