diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/SignIn/SignInPage.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/SignIn/SignInPage.razor.cs index 5bfd0acac0..2226374482 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/SignIn/SignInPage.razor.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/SignIn/SignInPage.razor.cs @@ -96,7 +96,7 @@ private async Task SocialSignIn(string provider) { try { - var port = localHttpServer.Start(CurrentCancellationToken); + var port = localHttpServer.ShouldUseForSocialSignIn() ? localHttpServer.Start(CurrentCancellationToken) : -1; var redirectUrl = await identityController.GetSocialSignInUri(provider, ReturnUrlQueryString, port is -1 ? null : port, CurrentCancellationToken); diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/SignUp/SignUpPage.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/SignUp/SignUpPage.razor.cs index 436fa869d1..ab9708aed1 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/SignUp/SignUpPage.razor.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/SignUp/SignUpPage.razor.cs @@ -75,7 +75,7 @@ private async Task SocialSignUp(string provider) { try { - var port = localHttpServer.Start(CurrentCancellationToken); + var port = localHttpServer.ShouldUseForSocialSignIn() ? localHttpServer.Start(CurrentCancellationToken) : -1; var redirectUrl = await identityController.GetSocialSignInUri(provider, localHttpPort: port is -1 ? null : port, cancellationToken: CurrentCancellationToken); diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/Contracts/ILocalHttpServer.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/Contracts/ILocalHttpServer.cs index ef429f3504..3463a79585 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/Contracts/ILocalHttpServer.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/Contracts/ILocalHttpServer.cs @@ -1,10 +1,18 @@ namespace Boilerplate.Client.Core.Services.Contracts; -/// -/// Social sign-in functions seamlessly on web browsers and on Android and iOS via universal app links. -/// However, for blazor hybrid, a local HTTP server is needed to ensure a smooth social sign-in experience. -/// public interface ILocalHttpServer { int Start(CancellationToken cancellationToken); + + /// + /// Social sign-in on the web version of the app uses simple redirects. However, for Android, iOS, Windows, and macOS, social sign-in requires an in-app or external browser. + /// + /// # Navigating Back to the App After Social Sign-In + /// 1. **Universal Deep Links**: Allow the app to directly handle specific web links (for iOS and Android apps). + /// 2. **Local HTTP Server**: Works similarly to how `git.exe` manages sign-ins with services like GitHub (supported on iOS, Android, Windows, and macOS). + /// + /// - **iOS, Windows, and macOS**: Use local HTTP server implementations in MAUI and Windows projects. + /// - **Android**: Use universal links. + /// + bool ShouldUseForSocialSignIn(); } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/NoopLocalHttpServer.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/NoopLocalHttpServer.cs index ed0060400a..faf3b701f9 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/NoopLocalHttpServer.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/NoopLocalHttpServer.cs @@ -1,10 +1,11 @@ namespace Boilerplate.Client.Core.Services; -/// -/// -/// The is specifically registered for Android, iOS, and Web, where a local HTTP server is unnecessary. -/// public partial class NoopLocalHttpServer : ILocalHttpServer { - public int Start(CancellationToken cancellationToken) => -1; + public int Start(CancellationToken cancellationToken) => throw new NotImplementedException(); + + /// + /// + /// + public bool ShouldUseForSocialSignIn() => false; } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/App.xaml.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/App.xaml.cs index b2ca0d9828..37f1f2a43b 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/App.xaml.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/App.xaml.cs @@ -4,6 +4,7 @@ using Maui.InAppReviews; using System.Runtime.InteropServices; //#endif +using Microsoft.Extensions.Logging; [assembly: XamlCompilation(XamlCompilationOptions.Compile)] @@ -15,6 +16,7 @@ public partial class App //#if (framework == 'net9.0') private readonly IStorageService storageService; //#endif + private readonly ILogger logger; private readonly IExceptionHandler exceptionHandler; private readonly IBitDeviceCoordinator deviceCoordinator; private readonly IStringLocalizer localizer; @@ -24,10 +26,12 @@ public App(MainPage mainPage, PubSubService pubSubService, IStorageService storageService, //#endif + ILogger logger, IExceptionHandler exceptionHandler, IBitDeviceCoordinator deviceCoordinator, IStringLocalizer localizer) { + this.logger = logger; this.localizer = localizer; //#if (framework == 'net9.0') this.storageService = storageService; @@ -72,11 +76,17 @@ protected override async void OnStart() //+:cnd:noEmit //#if (framework == 'net9.0') const int minimumSupportedWebViewVersion = 94; + // Download link for Android emulator (x86 or x86_64) + // https://www.apkmirror.com/apk/google-inc/chrome/chrome-94-0-4606-50-release/ + // https://www.apkmirror.com/apk/google-inc/android-system-webview/android-system-webview-94-0-4606-85-release/ //#elif (framework == 'net8.0') //#if (IsInsideProjectTemplate) /* //#endif const int minimumSupportedWebViewVersion = 84; + // Download link for Android emulator (x86 or x86_64) + // https://www.apkmirror.com/apk/google-inc/chrome/chrome-84-0-4147-89-release/ + // https://www.apkmirror.com/apk/google-inc/android-system-webview/android-system-webview-84-0-4147-111-release/ //#if (IsInsideProjectTemplate) */ //#endif @@ -85,8 +95,10 @@ protected override async void OnStart() if (Version.TryParse(Android.Webkit.WebView.CurrentWebViewPackage?.VersionName, out var webViewVersion) && webViewVersion.Major < minimumSupportedWebViewVersion) { + var webViewName = Android.Webkit.WebView.CurrentWebViewPackage.PackageName; + logger.LogWarning("{webViewName} version {version} is not supported.", webViewName, webViewVersion); await App.Current!.Windows[0].Page!.DisplayAlert("Boilerplate", localizer[nameof(AppStrings.UpdateWebViewThroughGooglePlay)], localizer[nameof(AppStrings.Ok)]); - await Launcher.OpenAsync($"https://play.google.com/store/apps/details?id={Android.Webkit.WebView.CurrentWebViewPackage.PackageName}"); + await Launcher.OpenAsync($"https://play.google.com/store/apps/details?id={webViewName}"); } //-:cnd:noEmit #endif diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/MauiProgram.Services.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/MauiProgram.Services.cs index c169776987..78ba7c58a2 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/MauiProgram.Services.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/MauiProgram.Services.cs @@ -39,12 +39,7 @@ public static void ConfigureServices(this MauiAppBuilder builder) return settings; }); services.AddSingleton(ITelemetryContext.Current!); - if (AppPlatform.IsAndroid is false) - { - // Handle social sign-in callback on local HTTP server. - // But in Android, leverage Universal Links for smoother sign-in flows. - services.AddSingleton(); - } + services.AddSingleton(); services.AddMauiBlazorWebView(); services.AddBlazorWebViewDeveloperTools(); diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Services/MauiLocalHttpServer.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Services/MauiLocalHttpServer.cs index d4c134d38b..64d5ed792e 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Services/MauiLocalHttpServer.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Services/MauiLocalHttpServer.cs @@ -1,14 +1,11 @@ using EmbedIO; using System.Net; -using System.Net.Sockets; using EmbedIO.Actions; +using System.Net.Sockets; using Boilerplate.Client.Core.Components; namespace Boilerplate.Client.Maui.Services; -/// -/// -/// public partial class MauiLocalHttpServer : ILocalHttpServer { [AutoInject] private IExceptionHandler exceptionHandler; @@ -89,4 +86,12 @@ private int GetAvailableTcpPort() l.Stop(); return port; } + + /// + /// + /// + public bool ShouldUseForSocialSignIn() + { + return AppPlatform.IsAndroid is false; + } } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/wwwroot/.well-known/assetlinks.json b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/wwwroot/.well-known/assetlinks.json index 3c198471ba..3994691b8d 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/wwwroot/.well-known/assetlinks.json +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/wwwroot/.well-known/assetlinks.json @@ -1,8 +1,7 @@ -[ +[ { "relation": [ - "delegate_permission/common.handle_all_urls", - "delegate_permission/common.get_login_creds" + "delegate_permission/common.handle_all_urls" ], "target": { "namespace": "android_app", diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/Services/WindowsLocalHttpServer.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/Services/WindowsLocalHttpServer.cs index cf52aed8b4..eec2676c23 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/Services/WindowsLocalHttpServer.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/Services/WindowsLocalHttpServer.cs @@ -1,6 +1,5 @@ using EmbedIO; using System.Net; -using System.Net.Http; using System.Net.Sockets; using EmbedIO.Actions; using Boilerplate.Client.Core.Components; @@ -69,6 +68,12 @@ public int Start(CancellationToken cancellationToken) return port; } + /// + /// + /// + + public bool ShouldUseForSocialSignIn() => true; + private int GetAvailableTcpPort() { using TcpListener l = new TcpListener(IPAddress.Loopback, 0);