diff --git a/com.onesignal.unity.android/Editor/OneSignalAndroidDependencies.cs b/com.onesignal.unity.android/Editor/OneSignalAndroidDependencies.cs index d67c7249b..7ae11429a 100644 --- a/com.onesignal.unity.android/Editor/OneSignalAndroidDependencies.cs +++ b/com.onesignal.unity.android/Editor/OneSignalAndroidDependencies.cs @@ -70,7 +70,7 @@ internal static void WriteDependencies() } } - internal const string Version = "5.10.2"; + internal const string Version = "5.11.0"; private static readonly string _dependenciesPath = Path.Combine( "Assets", diff --git a/com.onesignal.unity.android/Runtime/OneSignalAndroid.cs b/com.onesignal.unity.android/Runtime/OneSignalAndroid.cs index c8b7bc8f4..966c6eb19 100644 --- a/com.onesignal.unity.android/Runtime/OneSignalAndroid.cs +++ b/com.onesignal.unity.android/Runtime/OneSignalAndroid.cs @@ -25,8 +25,10 @@ * THE SOFTWARE. */ +using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using OneSignalSDK.Android.Debug; using OneSignalSDK.Android.InAppMessages; using OneSignalSDK.Android.LiveActivities; @@ -34,6 +36,7 @@ using OneSignalSDK.Android.Notifications; using OneSignalSDK.Android.Session; using OneSignalSDK.Android.User; +using OneSignalSDK.Android.Utilities; using OneSignalSDK.Debug; using OneSignalSDK.Debug.Utilities; using OneSignalSDK.InAppMessages; @@ -206,6 +209,67 @@ public override void Login(string externalId, string jwtBearerToken = null) _sdkClass.CallStatic("login", externalId, jwtBearerToken); } + public override Task LoginWithProfileAsync( + string externalId, + OneSignalUserProfile profile, + string jwtBearerToken = null + ) + { + var invalid = LoginResult.ValidateInputs(externalId, profile); + if (invalid != null) + return Task.FromResult(invalid); + + LoginResultContinuation continuation; + try + { + continuation = new LoginResultContinuation(); + } + catch (Exception e) + { + SDKDebug.Error("loginWithProfile failed: " + e.Message); + return Task.FromResult(LoginResult.Failure("UNKNOWN", e.Message)); + } + + try + { + // Resolved before the call so nothing after it can fail and mask a native result. + using var resultClass = new AndroidJavaClass(SDKPackage + ".OneSignalResult"); + using var tags = (profile.Tags ?? new Dictionary()).ToMap(); + using var aliases = (profile.Aliases ?? new Dictionary()).ToMap(); + using var profileClass = new AndroidJavaClass(SDKPackage + ".OneSignalUserProfile"); + using var builder = profileClass.CallStatic("builder"); + if (profile.Email != null) + builder.Call("setEmail", profile.Email)?.Dispose(); + if (profile.Sms != null) + builder.Call("setSms", profile.Sms)?.Dispose(); + builder.Call("setTags", tags)?.Dispose(); + builder.Call("setAliases", aliases)?.Dispose(); + using var nativeProfile = builder.Call("build"); + using var proxy = continuation.CreateProxy(); + using var returned = _sdkClass.CallStatic( + "login", + externalId, + nativeProfile, + jwtBearerToken, + proxy + ); + + // A suspend call returns either COROUTINE_SUSPENDED or its result. Testing for the + // result type survives R8 renaming of the kotlin stdlib marker class. + if ( + returned != null + && AndroidJNI.IsInstanceOf(returned.GetRawObject(), resultClass.GetRawClass()) + ) + continuation.Resolve(returned); + } + catch (Exception e) + { + continuation.Fail(e.Message); + } + + return continuation.Result; + } + public override void Logout() { _sdkClass.CallStatic("logout"); diff --git a/com.onesignal.unity.android/Runtime/Utilities/AndroidJavaProxy.cs b/com.onesignal.unity.android/Runtime/Utilities/AndroidJavaProxy.cs index 6fdb094f0..500a277ee 100644 --- a/com.onesignal.unity.android/Runtime/Utilities/AndroidJavaProxy.cs +++ b/com.onesignal.unity.android/Runtime/Utilities/AndroidJavaProxy.cs @@ -2,6 +2,8 @@ using System.Runtime.CompilerServices; using System.Threading.Tasks; using Laters; +using OneSignalSDK.Debug.Utilities; +using OneSignalSDK.User; using UnityEngine; namespace OneSignalSDK.Android.Utilities @@ -66,6 +68,74 @@ public void accept(AndroidJavaObject obj) } } + /// + /// Completes with the native OneSignalResult, whether it arrives through the continuation or + /// is returned synchronously by the suspend call. Completes exactly once and never throws. + /// + internal sealed class LoginResultContinuation : AndroidJavaProxy + { + private readonly TaskCompletionSource _completion = + new TaskCompletionSource( + TaskCreationOptions.RunContinuationsAsynchronously + ); + + public Task Result => _completion.Task; + + public LoginResultContinuation() + : base("java.util.function.Consumer") { } + + public AndroidJavaObject CreateProxy() + { + using var continuation = new AndroidJavaClass("com.onesignal.Continue"); + return continuation.CallStatic("with", this); + } + + public void accept(AndroidJavaObject continueResult) + { + try + { + if (continueResult.Call("isSuccess")) + { + using var result = continueResult.Call("getData"); + Resolve(result); + } + else + { + using var throwable = continueResult.Call("getThrowable"); + Fail( + throwable?.Call("getMessage") + ?? throwable?.Call("toString") + ?? "loginWithProfile failed" + ); + } + } + catch (Exception e) + { + Fail(e.Message); + } + } + + public void Resolve(AndroidJavaObject oneSignalResult) + { + try + { + using var map = oneSignalResult.Call("toMap"); + using var json = new AndroidJavaObject("org.json.JSONObject", map); + _completion.TrySetResult(LoginResult.FromJson(json.Call("toString"))); + } + catch (Exception e) + { + Fail(e.Message); + } + } + + public void Fail(string message) + { + if (_completion.TrySetResult(LoginResult.Failure("UNKNOWN", message))) + SDKDebug.Error("loginWithProfile failed: " + message); + } + } + public sealed class BoolContinuation { public AndroidJavaObject Proxy { get; } diff --git a/com.onesignal.unity.core/Editor/Platform/OneSignalNative.cs b/com.onesignal.unity.core/Editor/Platform/OneSignalNative.cs index 84cd07c61..cda4354bf 100755 --- a/com.onesignal.unity.core/Editor/Platform/OneSignalNative.cs +++ b/com.onesignal.unity.core/Editor/Platform/OneSignalNative.cs @@ -115,6 +115,12 @@ public override void Initialize(string appId) public override void Login(string externalId, string jwtBearerToken = null) { } + public override Task LoginWithProfileAsync( + string externalId, + OneSignalUserProfile profile, + string jwtBearerToken = null + ) => Task.FromResult(LoginResult.Unsupported()); + public override void Logout() { } } } diff --git a/com.onesignal.unity.core/Runtime/OneSignal.cs b/com.onesignal.unity.core/Runtime/OneSignal.cs index a4b905fa9..8136e58a0 100755 --- a/com.onesignal.unity.core/Runtime/OneSignal.cs +++ b/com.onesignal.unity.core/Runtime/OneSignal.cs @@ -26,6 +26,7 @@ */ using System.Collections.Generic; +using System.Threading.Tasks; using OneSignalSDK.Debug; using OneSignalSDK.InAppMessages; using OneSignalSDK.LiveActivities; @@ -175,6 +176,19 @@ public static void Login(string externalId, string jwtBearerToken = null) OneSignal.Default.Login(externalId, jwtBearerToken); } + /// + /// Logs in [externalId] and applies [profile] in one operation. Android only for now: + /// on other platforms the task completes with a failed result and the native SDK is not called. + /// + public static Task LoginWithProfileAsync( + string externalId, + OneSignalUserProfile profile, + string jwtBearerToken = null + ) + { + return OneSignal.Default.LoginWithProfileAsync(externalId, profile, jwtBearerToken); + } + /// /// Logout the user previously logged in via [login]. The [user] property now references /// a new device-scoped user. A device-scoped user has no user identity that can later diff --git a/com.onesignal.unity.core/Runtime/OneSignalPlatform.cs b/com.onesignal.unity.core/Runtime/OneSignalPlatform.cs index 01aede8bb..a7e67187b 100644 --- a/com.onesignal.unity.core/Runtime/OneSignalPlatform.cs +++ b/com.onesignal.unity.core/Runtime/OneSignalPlatform.cs @@ -27,6 +27,7 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using OneSignalSDK.Debug; using OneSignalSDK.InAppMessages; using OneSignalSDK.LiveActivities; @@ -147,6 +148,16 @@ protected static void _init(string appId) /// public abstract void Login(string externalId, string jwtBearerToken = null); + /// + /// Logs in [externalId] and applies [profile] in one operation. Android only for now: + /// on other platforms the task completes with a failed result and the native SDK is not called. + /// + public abstract Task LoginWithProfileAsync( + string externalId, + OneSignalUserProfile profile, + string jwtBearerToken = null + ); + /// /// Logout the user previously logged in via [login]. The [user] property now references /// a new device-scoped user. A device-scoped user has no user identity that can later diff --git a/com.onesignal.unity.core/Runtime/User/Models/LoginResult.cs b/com.onesignal.unity.core/Runtime/User/Models/LoginResult.cs new file mode 100644 index 000000000..b4168d389 --- /dev/null +++ b/com.onesignal.unity.core/Runtime/User/Models/LoginResult.cs @@ -0,0 +1,198 @@ +using System; +using System.Collections.Generic; + +namespace OneSignalSDK.User +{ + public sealed class LoginData + { + public string OneSignalId { get; } + public string ExternalId { get; } + public string EmailSubscriptionId { get; } + public string SmsSubscriptionId { get; } + + internal LoginData( + string onesignalId, + string externalId, + string emailSubscriptionId, + string smsSubscriptionId + ) + { + OneSignalId = onesignalId; + ExternalId = externalId; + EmailSubscriptionId = emailSubscriptionId; + SmsSubscriptionId = smsSubscriptionId; + } + } + + public sealed class OneSignalError + { + /// + /// One of BACKEND_ERROR, INVALID_ARGUMENT, NOT_INITIALIZED, STORAGE_LOCKED, UNKNOWN. + /// + public string Code { get; } + + /// CLIENT or BACKEND. + public string Source { get; } + public string BackendCode { get; } + public int? HttpStatus { get; } + public string Message { get; } + public IReadOnlyDictionary Meta { get; } + + internal OneSignalError( + string code, + string source, + string backendCode, + int? httpStatus, + string message, + IReadOnlyDictionary meta + ) + { + Code = code; + Source = source; + BackendCode = backendCode; + HttpStatus = httpStatus; + Message = message; + Meta = meta; + } + } + + public sealed class LoginResult + { + internal const string UnsupportedPlatformMessage = + "loginWithProfile is only supported on Android"; + + public bool Success { get; } + + /// Null unless is true. + public LoginData Data { get; } + + /// Empty on success, never empty on failure. + public IReadOnlyList Errors { get; } + + private LoginResult(bool success, LoginData data, IReadOnlyList errors) + { + Success = success; + Data = data; + Errors = errors; + } + + internal static LoginResult Failure(string code, string message) => + new LoginResult( + false, + null, + new[] { new OneSignalError(code, "CLIENT", null, null, message, null) } + ); + + internal static LoginResult Unsupported() => Failure("UNKNOWN", UnsupportedPlatformMessage); + + /// Returns an INVALID_ARGUMENT failure, or null when the inputs are usable. + internal static LoginResult ValidateInputs(string externalId, OneSignalUserProfile profile) + { + const string api = "loginWithProfile"; + if (InputGuard.IsMissing(externalId, api + ": externalId")) + return Failure("INVALID_ARGUMENT", "externalId is required"); + if (InputGuard.IsNull(profile, api + ": profile")) + return Failure("INVALID_ARGUMENT", "profile is required"); + if ( + profile.Tags != null + && InputGuard.HasMissingEntries(profile.Tags, api + ": tags", true) + ) + return Failure("INVALID_ARGUMENT", "tags must not contain null entries"); + if ( + profile.Aliases != null + && InputGuard.HasMissingEntries(profile.Aliases, api + ": aliases", false) + ) + return Failure("INVALID_ARGUMENT", "aliases must not contain empty entries"); + return null; + } + + internal static LoginResult FromJson(string json) + { + Dictionary root; + try + { + root = Json.Deserialize(json) as Dictionary; + } + catch (Exception) + { + root = null; + } + + if (root == null || !(_get(root, "success") is bool success)) + return Failure("UNKNOWN", "Malformed loginWithProfile result"); + + if (success) + { + var data = _parseData(_get(root, "data")); + var strayErrors = _get(root, "error") is List stray && stray.Count > 0; + return data != null && !strayErrors + ? new LoginResult(true, data, Array.Empty()) + : Failure("UNKNOWN", "Malformed loginWithProfile result data"); + } + + var errors = new List(); + if (_get(root, "error") is List entries) + { + foreach (var entry in entries) + errors.Add(_parseError(entry)); + } + + return errors.Count > 0 + ? new LoginResult(false, null, errors) + : Failure("UNKNOWN", "loginWithProfile failed without an error"); + } + + private static LoginData _parseData(object value) + { + if ( + !(value is Dictionary data) + || !(_get(data, "onesignalId") is string onesignalId) + || !(_get(data, "externalId") is string externalId) + ) + return null; + + return new LoginData( + onesignalId, + externalId, + _get(data, "emailSubscriptionId") as string, + _get(data, "smsSubscriptionId") as string + ); + } + + private static OneSignalError _parseError(object value) + { + if (!(value is Dictionary error)) + return new OneSignalError( + "UNKNOWN", + "CLIENT", + null, + null, + "Malformed loginWithProfile error", + null + ); + + int? httpStatus = null; + switch (_get(error, "httpStatus")) + { + case long l when l >= int.MinValue && l <= int.MaxValue: + httpStatus = (int)l; + break; + case double d when d >= int.MinValue && d <= int.MaxValue: + httpStatus = (int)d; + break; + } + + return new OneSignalError( + _get(error, "code") as string ?? "UNKNOWN", + _get(error, "source") as string ?? "CLIENT", + _get(error, "backendCode") as string, + httpStatus, + _get(error, "message") as string ?? "", + _get(error, "meta") as Dictionary + ); + } + + private static object _get(Dictionary source, string key) => + source.TryGetValue(key, out var value) ? value : null; + } +} diff --git a/com.onesignal.unity.core/Runtime/User/Models/LoginResult.cs.meta b/com.onesignal.unity.core/Runtime/User/Models/LoginResult.cs.meta new file mode 100644 index 000000000..eb03b255a --- /dev/null +++ b/com.onesignal.unity.core/Runtime/User/Models/LoginResult.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ae5425d9a7594bdfad50f3de6dc22ed3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.onesignal.unity.core/Runtime/User/Models/OneSignalUserProfile.cs b/com.onesignal.unity.core/Runtime/User/Models/OneSignalUserProfile.cs new file mode 100644 index 000000000..a1049c8fe --- /dev/null +++ b/com.onesignal.unity.core/Runtime/User/Models/OneSignalUserProfile.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace OneSignalSDK.User +{ + /// + /// User data applied together with a login. Blank email and SMS values are treated as absent. + /// + public sealed class OneSignalUserProfile + { + public string Email { get; set; } + public string Sms { get; set; } + public Dictionary Tags { get; set; } + public Dictionary Aliases { get; set; } + } +} diff --git a/com.onesignal.unity.core/Runtime/User/Models/OneSignalUserProfile.cs.meta b/com.onesignal.unity.core/Runtime/User/Models/OneSignalUserProfile.cs.meta new file mode 100644 index 000000000..d69680c18 --- /dev/null +++ b/com.onesignal.unity.core/Runtime/User/Models/OneSignalUserProfile.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fabd7f982e1a4391b40a013e81aae42e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.onesignal.unity.ios/Runtime/OneSignaliOS.cs b/com.onesignal.unity.ios/Runtime/OneSignaliOS.cs index e7d6eca85..b10fa3721 100644 --- a/com.onesignal.unity.ios/Runtime/OneSignaliOS.cs +++ b/com.onesignal.unity.ios/Runtime/OneSignaliOS.cs @@ -197,6 +197,12 @@ public override void Login(string externalId, string jwtBearerToken = null) } } + public override Task LoginWithProfileAsync( + string externalId, + OneSignalUserProfile profile, + string jwtBearerToken = null + ) => Task.FromResult(LoginResult.Unsupported()); + public override void Logout() { _oneSignalLogout(); diff --git a/examples/build.md b/examples/build.md index 9594a4718..99a853c11 100644 --- a/examples/build.md +++ b/examples/build.md @@ -69,6 +69,7 @@ using OneSignalSDK.Debug.Models; | Operation | SDK Call | |---|---| | LoginUser(externalUserId) | `OneSignal.Login(externalUserId)` | +| LoginUserWithProfile(externalUserId, email, sms, jwt) | `await OneSignal.LoginWithProfileAsync(externalUserId, profile, jwt)` (Android only) | | LogoutUser() | `OneSignal.Logout()` | | AddAlias(label, id) | `OneSignal.User.AddAlias(label, id)` | | AddAliases(aliases) | `OneSignal.User.AddAliases(aliases)` | diff --git a/examples/demo/Assets/OneSignal/Editor/OneSignalAndroidDependencies.xml b/examples/demo/Assets/OneSignal/Editor/OneSignalAndroidDependencies.xml index d6ca44396..d6cf720fb 100644 --- a/examples/demo/Assets/OneSignal/Editor/OneSignalAndroidDependencies.xml +++ b/examples/demo/Assets/OneSignal/Editor/OneSignalAndroidDependencies.xml @@ -3,6 +3,6 @@ https://repo.maven.apache.org/maven2 - + diff --git a/examples/demo/Assets/Plugins/Android/mainTemplate.gradle b/examples/demo/Assets/Plugins/Android/mainTemplate.gradle index cce5eb394..90c8d3a2c 100644 --- a/examples/demo/Assets/Plugins/Android/mainTemplate.gradle +++ b/examples/demo/Assets/Plugins/Android/mainTemplate.gradle @@ -8,7 +8,7 @@ apply from: '../shared/common.gradle' dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) // Android Resolver Dependencies Start - implementation 'com.onesignal:OneSignal:5.10.2' // Assets/OneSignal/Editor/OneSignalAndroidDependencies.xml:6 + implementation 'com.onesignal:OneSignal:5.11.0' // Assets/OneSignal/Editor/OneSignalAndroidDependencies.xml:6 // Android Resolver Dependencies End **DEPS**} diff --git a/examples/demo/Assets/Scripts/UI/Dialogs/LoginWithProfileDialog.cs b/examples/demo/Assets/Scripts/UI/Dialogs/LoginWithProfileDialog.cs new file mode 100644 index 000000000..418c4edd7 --- /dev/null +++ b/examples/demo/Assets/Scripts/UI/Dialogs/LoginWithProfileDialog.cs @@ -0,0 +1,74 @@ +using System; +using UnityEngine.UIElements; + +namespace OneSignalDemo.UI.Dialogs +{ + public class LoginWithProfileDialog : DialogBase + { + private readonly Action _onConfirm; + private TextField _externalIdField; + private TextField _emailField; + private TextField _smsField; + private TextField _jwtField; + private Button _confirmButton; + + public LoginWithProfileDialog(Action onConfirm) + { + _onConfirm = onConfirm; + } + + protected override void BuildContent(VisualElement container) + { + var title = new Label("Login With Profile"); + title.AddToClassList("dialog-title"); + title.AddToClassList("text-dialog-title"); + container.Add(title); + + _externalIdField = CreateTextField("External User Id", "composite_login_user_id_input"); + _externalIdField.RegisterValueChangedCallback(_ => ValidateInput()); + container.Add(_externalIdField); + + _emailField = CreateTextField("Email (optional)", "composite_login_email_input"); + container.Add(_emailField); + + _smsField = CreateTextField("SMS (optional)", "composite_login_sms_input"); + container.Add(_smsField); + + _jwtField = CreateTextField("JWT Token (optional)", "composite_login_jwt_input"); + container.Add(_jwtField); + + var actions = new VisualElement(); + actions.AddToClassList("dialog-actions"); + + actions.Add(CreateCancelButton()); + + _confirmButton = CreateConfirmButton("Login", OnConfirm); + _confirmButton.name = "composite_login_confirm_button"; + _confirmButton.SetEnabled(false); + actions.Add(_confirmButton); + + container.Add(actions); + } + + private void ValidateInput() + { + _confirmButton?.SetEnabled(!string.IsNullOrWhiteSpace(_externalIdField?.value)); + } + + private void OnConfirm() + { + var externalId = _externalIdField?.value?.Trim(); + if (string.IsNullOrEmpty(externalId)) + return; + + _externalIdField?.Blur(); + _onConfirm?.Invoke( + externalId, + _emailField?.value?.Trim(), + _smsField?.value?.Trim(), + _jwtField?.value?.Trim() + ); + Dismiss(); + } + } +} diff --git a/examples/demo/Assets/Scripts/UI/Dialogs/LoginWithProfileDialog.cs.meta b/examples/demo/Assets/Scripts/UI/Dialogs/LoginWithProfileDialog.cs.meta new file mode 100644 index 000000000..8fb74f862 --- /dev/null +++ b/examples/demo/Assets/Scripts/UI/Dialogs/LoginWithProfileDialog.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3c04d94be0a044ed9f99cfb1579ed128 \ No newline at end of file diff --git a/examples/demo/Assets/Scripts/UI/Sections/UserSectionController.cs b/examples/demo/Assets/Scripts/UI/Sections/UserSectionController.cs index 3968026bd..952e1737e 100644 --- a/examples/demo/Assets/Scripts/UI/Sections/UserSectionController.cs +++ b/examples/demo/Assets/Scripts/UI/Sections/UserSectionController.cs @@ -57,6 +57,16 @@ private VisualElement BuildSection() ); section.Add(_loginButton); +#if UNITY_ANDROID + section.Add( + SectionBuilder.CreateDestructiveButton( + "LOGIN WITH PROFILE", + "composite_login_button", + ShowLoginWithProfileDialog + ) + ); +#endif + _logoutButton = SectionBuilder.CreateDestructiveButton( "LOGOUT USER", "logout_user_button", @@ -79,6 +89,15 @@ private void ShowLoginDialog() dialog.Show(_dialogRoot); } + private void ShowLoginWithProfileDialog() + { + var dialog = new LoginWithProfileDialog( + (externalId, email, sms, jwt) => + _viewModel.LoginUserWithProfile(externalId, email, sms, jwt) + ); + dialog.Show(_dialogRoot); + } + public void Refresh() { _statusValue.text = _viewModel.IsLoggedIn ? "Logged In" : "Anonymous"; diff --git a/examples/demo/Assets/Scripts/ViewModels/AppViewModel.cs b/examples/demo/Assets/Scripts/ViewModels/AppViewModel.cs index 21df63a58..32217555f 100644 --- a/examples/demo/Assets/Scripts/ViewModels/AppViewModel.cs +++ b/examples/demo/Assets/Scripts/ViewModels/AppViewModel.cs @@ -161,6 +161,58 @@ public void LoginUser(string externalUserId) NotifyStateChanged(); } + public async void LoginUserWithProfile( + string externalUserId, + string email, + string sms, + string jwtBearerToken + ) + { + externalUserId = externalUserId?.Trim(); + if (string.IsNullOrEmpty(externalUserId)) + return; + + ClearUserData(); + SetLoading(true); + + try + { + var profile = new OneSignalUserProfile + { + Email = string.IsNullOrWhiteSpace(email) ? null : email.Trim(), + Sms = string.IsNullOrWhiteSpace(sms) ? null : sms.Trim(), + }; + var result = await OneSignal.LoginWithProfileAsync( + externalUserId, + profile, + string.IsNullOrWhiteSpace(jwtBearerToken) ? null : jwtBearerToken.Trim() + ); + + if (!result.Success) + { + foreach (var error in result.Errors) + Debug.LogError( + $"[{Tag}] Login with profile error: {error.Code} ({error.Source}) {error.Message}" + ); + SetLoading(false); + return; + } + + _prefs.ExternalUserId = externalUserId; + _externalUserId = externalUserId; + Debug.Log($"[{Tag}] Logged in with profile as: {externalUserId}"); + NotifyStateChanged(); + + // The user 'change' listener does not fire when the external id is unchanged. + await FetchUserDataFromApi(); + } + catch (Exception ex) + { + Debug.LogError($"[{Tag}] Login with profile error: {ex.Message}"); + SetLoading(false); + } + } + public void LogoutUser() { OneSignal.Logout(); diff --git a/examples/demo/ProjectSettings/AndroidResolverDependencies.xml b/examples/demo/ProjectSettings/AndroidResolverDependencies.xml index 35bc23a24..331a0be10 100644 --- a/examples/demo/ProjectSettings/AndroidResolverDependencies.xml +++ b/examples/demo/ProjectSettings/AndroidResolverDependencies.xml @@ -1,6 +1,6 @@ - com.onesignal:OneSignal:5.10.2 + com.onesignal:OneSignal:5.11.0