diff --git a/src/MobileUI/Common/ApplicationExtension.cs b/src/MobileUI/Common/ApplicationExtension.cs new file mode 100644 index 000000000..75a00e7ff --- /dev/null +++ b/src/MobileUI/Common/ApplicationExtension.cs @@ -0,0 +1,16 @@ +namespace SSW.Rewards.Mobile.Common; +public static class ApplicationExtension +{ + public static async Task InitializeMainPage(this Application application) + { + if (!(application.MainPage is AppShell)) + { + await MainThread.InvokeOnMainThreadAsync(() => + { + application.MainPage = new AppShell(); + }); + } + + await Shell.Current.GoToAsync("//main"); + } +} diff --git a/src/MobileUI/Features/App.xaml.cs b/src/MobileUI/Features/App.xaml.cs index 9289b621b..991a5d791 100644 --- a/src/MobileUI/Features/App.xaml.cs +++ b/src/MobileUI/Features/App.xaml.cs @@ -45,24 +45,32 @@ protected override async void OnAppLinkRequestReceived(Uri uri) { base.OnAppLinkRequestReceived(uri); - if (uri.Scheme != ApiClientConstants.RewardsQRCodeProtocol) + if (uri.Scheme == ApiClientConstants.RewardsQRCodeProtocol) { - return; - } - - var queryDictionary = System.Web.HttpUtility.ParseQueryString(uri.Query); - var code = queryDictionary.Get(ApiClientConstants.RewardsQRCodeProtocolQueryName); + var queryDictionary = System.Web.HttpUtility.ParseQueryString(uri.Query); + var code = queryDictionary.Get(ApiClientConstants.RewardsQRCodeProtocolQueryName); - if (_authService.IsLoggedIn) - { - var vm = ActivatorUtilities.CreateInstance(_provider); - var popup = new PopupPages.ScanResult(vm, code); - await MopupService.Instance.PushAsync(popup); + if (_authService.IsLoggedIn) + { + var vm = ActivatorUtilities.CreateInstance(_provider); + var popup = new PopupPages.ScanResult(vm, code); + await MopupService.Instance.PushAsync(popup); + } + else + { + ((LoginPage)MainPage)?.QueueCodeScan(code); + } } - else + else if ($"{uri.Scheme}://{uri.Host}" == Constants.AuthRedirectUrl) { - ((LoginPage)MainPage)?.QueueCodeScan(code); - } + var queryDictionary = System.Web.HttpUtility.ParseQueryString(uri.Query); + var token = queryDictionary.Get("token"); + + if (!string.IsNullOrEmpty(token)) + { + await _authService.AutologinAsync(token); + } + } } private async Task CheckApiCompatibilityAsync() diff --git a/src/MobileUI/Features/Login/LoginPageViewModel.cs b/src/MobileUI/Features/Login/LoginPageViewModel.cs index 73eb7446a..745e08435 100644 --- a/src/MobileUI/Features/Login/LoginPageViewModel.cs +++ b/src/MobileUI/Features/Login/LoginPageViewModel.cs @@ -2,6 +2,7 @@ using CommunityToolkit.Mvvm.Input; using Microsoft.AppCenter.Crashes; using Mopups.Services; +using SSW.Rewards.Mobile.Common; namespace SSW.Rewards.Mobile.ViewModels; @@ -65,8 +66,14 @@ private async Task LoginTapped() if (status != ApiStatus.Success) { await WaitForWindowClose(); - var alert = statusAlerts.GetValueOrDefault(status, (Title: "Unexpected Error", Message: "Something went wrong there, please try again later.")); - await App.Current.MainPage.DisplayAlert(alert.Title, alert.Message, "OK"); + + // Only display error if user is not logged in. + // Autologin will fall here, if login page is opened, despite being successfull. + if (_authService.IsLoggedIn && _authService.HasCachedAccount) + { + var alert = statusAlerts.GetValueOrDefault(status, (Title: "Unexpected Error", Message: "Something went wrong there, please try again later.")); + await App.Current.MainPage.DisplayAlert(alert.Title, alert.Message, "OK"); + } } else { @@ -130,8 +137,8 @@ await Application.Current.MainPage.DisplayAlert("Login Failure", private async Task OnAfterLogin() { - Application.Current.MainPage = new AppShell(); - await Shell.Current.GoToAsync("//main"); + await Application.Current.InitializeMainPage(); + var granted = await _permissionsService.CheckAndRequestPermission(); if (granted) { diff --git a/src/MobileUI/Platforms/Android/MainActivity.cs b/src/MobileUI/Platforms/Android/MainActivity.cs index 72a20db14..2e9d50729 100644 --- a/src/MobileUI/Platforms/Android/MainActivity.cs +++ b/src/MobileUI/Platforms/Android/MainActivity.cs @@ -1,11 +1,7 @@ using Android.App; using Android.Content.PM; -using Android.Gms.Extensions; using Android.OS; -using Firebase.Messaging; using Microsoft.Maui.Controls.Compatibility.Platform.Android; -using Microsoft.Maui.Controls.PlatformConfiguration; -using SSW.Rewards.Mobile.Platforms.Android; using Color = Microsoft.Maui.Graphics.Color; namespace SSW.Rewards.Mobile; diff --git a/src/MobileUI/Services/AuthenticationService.cs b/src/MobileUI/Services/AuthenticationService.cs index 7082b9c84..efe68bdb2 100644 --- a/src/MobileUI/Services/AuthenticationService.cs +++ b/src/MobileUI/Services/AuthenticationService.cs @@ -1,13 +1,16 @@ +using System.IdentityModel.Tokens.Jwt; using IdentityModel.Client; using IdentityModel.OidcClient; using IdentityModel.OidcClient.Results; using Microsoft.AppCenter.Crashes; +using SSW.Rewards.Mobile.Common; using IBrowser = IdentityModel.OidcClient.Browser.IBrowser; namespace SSW.Rewards.Mobile.Services; public interface IAuthenticationService { + Task AutologinAsync(string accessToken); Task SignInAsync(); Task GetAccessToken(); Task SignOut(); @@ -42,6 +45,44 @@ public AuthenticationService(IBrowser browser) }; } + public async Task AutologinAsync(string accessToken) + { + try + { + var tokenHandler = new JwtSecurityTokenHandler(); + var jwtToken = tokenHandler.ReadJwtToken(accessToken); + + _accessToken = accessToken; + _tokenExpiry = jwtToken.ValidTo; + + try + { + Preferences.Set(nameof(HasCachedAccount), true); + DetailsUpdated?.Invoke(this, EventArgs.Empty); + + await Application.Current.InitializeMainPage(); + } + catch (Exception ex) + { + Crashes.TrackError(new Exception("Failed to set a logged-in state")); + + // TECH DEBT: Workaround for iOS since calling DisplayAlert while a Safari web view is in + // the process of closing causes the alert to never appear and the await call never returns. + if (DeviceInfo.Platform == DevicePlatform.iOS) + { + await Task.Delay(1000); + } + + await App.Current.MainPage.DisplayAlert("Login Failure", "There seems to have been a problem logging you in. Please try again.", "OK"); + } + } + catch (Exception ex) + { + Crashes.TrackError(new Exception($"AuthDebug: unknown exception was thrown during SignIn ${ex.Message}; ${ex.StackTrace}")); + await SignOut(); + } + } + public async Task SignInAsync() { try