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..28b302e0fe 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.UseLocalHttpServerForSocialSignIn() ? 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..8d12a6ee53 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.UseLocalHttpServerForSocialSignIn() ? 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..02622c2d9e 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 UseLocalHttpServerForSocialSignIn(); } 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..6453be400b 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 UseLocalHttpServerForSocialSignIn() => 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..132d2742fd 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 @@ -2,6 +2,7 @@ //#if (framework == 'net9.0') using Maui.AppStores; using Maui.InAppReviews; +using Microsoft.Extensions.Logging; using System.Runtime.InteropServices; //#endif @@ -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,6 +95,7 @@ protected override async void OnStart() if (Version.TryParse(Android.Webkit.WebView.CurrentWebViewPackage?.VersionName, out var webViewVersion) && webViewVersion.Major < minimumSupportedWebViewVersion) { + logger.LogWarning("Web view version {version} is not supported", 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}"); } 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..eb413360eb 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,16 +1,15 @@ using EmbedIO; using System.Net; -using System.Net.Sockets; using EmbedIO.Actions; +using System.Net.Sockets; +using Microsoft.Extensions.Logging; using Boilerplate.Client.Core.Components; namespace Boilerplate.Client.Maui.Services; -/// -/// -/// public partial class MauiLocalHttpServer : ILocalHttpServer { + [AutoInject] private ILogger logger; [AutoInject] private IExceptionHandler exceptionHandler; [AutoInject] private AbsoluteServerAddressProvider absoluteServerAddress; @@ -89,4 +88,12 @@ private int GetAvailableTcpPort() l.Stop(); return port; } + + /// + /// + /// + public bool UseLocalHttpServerForSocialSignIn() + { + 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..9776fc1f50 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,4 +1,11 @@ [ + { + "relation": [ "delegate_permission/common.get_login_creds" ], + "target": { + "namespace": "web", + "site": "https://use-your-web-app-url-here.com/" + } + }, { "relation": [ "delegate_permission/common.handle_all_urls", @@ -14,7 +21,8 @@ }, { "relation": [ - "delegate_permission/common.handle_all_urls" + "delegate_permission/common.handle_all_urls", + "delegate_permission/common.get_login_creds" ], "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..98eccd614f 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 UseLocalHttpServerForSocialSignIn() => true; + private int GetAvailableTcpPort() { using TcpListener l = new TcpListener(IPAddress.Loopback, 0);