diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/settings.VisualStudio.json b/src/Templates/Boilerplate/Bit.Boilerplate/settings.VisualStudio.json index b49045bc2a..1cbd73147e 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/settings.VisualStudio.json +++ b/src/Templates/Boilerplate/Bit.Boilerplate/settings.VisualStudio.json @@ -1,6 +1,7 @@ /* Visual Studio Settings File */ { - "debugging.general.disableJITOptimization": true, - "environment.documents.saveWithSpecificEncoding": true, - "environment.documents.saveEncoding": "utf-8;65001" + "languages.defaults.general.lineNumbers": true, + "debugging.general.disableJITOptimization": true, + "environment.documents.saveWithSpecificEncoding": true, + "environment.documents.saveEncoding": "utf-8;65001" } \ No newline at end of file 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 565bc7d98a..aca3662a8e 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 @@ -152,7 +152,7 @@ private async Task HandleOnSocialSignIn(string provider) { try { - var port = localHttpServer.ShouldUseForSocialSignIn() ? localHttpServer.EnsureStarted() : -1; + var port = localHttpServer.EnsureStarted(); 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 082d8c7385..81416738c1 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.ShouldUseForSocialSignIn() ? localHttpServer.EnsureStarted() : -1; + var port = localHttpServer.EnsureStarted(); 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/Scripts/ExternalJsRunner.ts b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Scripts/ExternalJsRunner.ts index 4cca893542..70d0651180 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Scripts/ExternalJsRunner.ts +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Scripts/ExternalJsRunner.ts @@ -1,4 +1,4 @@ -// Checkout external-js-runner.html +// Checkout external-js-runner.html class ExternalJsRunner { public static async run() { const host = window.origin.replace('http://', ''); @@ -12,11 +12,10 @@ class ExternalJsRunner { } else if (request.type == 'createCredential') { result = await WebAuthn.createCredential(request.options); } else if (request.type == 'close') { - result = {}; localWebSocket.close(); - setTimeout(() => { - window.close(); - }, 100); + window.close(); + window.location.assign('/close-browser'); + return; } localWebSocket.send(JSON.stringify({ body: result })); } 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 5c7eed8a03..e6fe6ea4d8 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 @@ -7,16 +7,4 @@ public interface ILocalHttpServer : IAsyncDisposable int Port { get; } string? Origin { get; } - - /// - /// 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/HttpMessageHandlers/RequestHeadersDelegatingHandler.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/HttpMessageHandlers/RequestHeadersDelegatingHandler.cs index 63cf728576..6925767e42 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/HttpMessageHandlers/RequestHeadersDelegatingHandler.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/HttpMessageHandlers/RequestHeadersDelegatingHandler.cs @@ -12,7 +12,7 @@ protected override async Task SendAsync(HttpRequestMessage request.SetBrowserRequestCredentials(BrowserRequestCredentials.Omit); request.SetBrowserResponseStreamingEnabled(true); - request.Version = HttpVersion.Version30; + request.Version = HttpVersion.Version20; request.VersionPolicy = HttpVersionPolicy.RequestVersionOrLower; if (request.Headers.UserAgent.Any() is false) 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 caf16cd168..248dc5832a 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 @@ -8,10 +8,5 @@ public partial class NoOpLocalHttpServer : ILocalHttpServer public int Port => -1; - /// - /// - /// - public bool ShouldUseForSocialSignIn() => false; - public ValueTask DisposeAsync() => ValueTask.CompletedTask; } 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 ccc1da4e75..c55dc10154 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 @@ -33,23 +33,7 @@ public int EnsureStarted() { try { - // Redirect to SocialSignedInPage.razor that will close the browser window. - var url = new Uri(absoluteServerAddress, $"/api/Identity/SocialSignedIn?culture={CultureInfo.CurrentUICulture.Name}").ToString(); - ctx.Redirect(url); - - if (AppPlatform.IsIOS) - { - // SocialSignedInPage.razor's `window.close()` does NOT work on iOS's in app browser. - await MainThread.InvokeOnMainThreadAsync(() => - { -#if iOS - if (UIKit.UIApplication.SharedApplication.KeyWindow?.RootViewController?.PresentedViewController is SafariServices.SFSafariViewController controller) - { - controller.DismissViewController(animated: true, completionHandler: null); - } -#endif - }); - } + ctx.Redirect("/close-browser"); _ = Task.Delay(1) .ContinueWith(async _ => @@ -65,10 +49,42 @@ await MainThread.InvokeOnMainThreadAsync(async () => exceptionHandler.Handle(exp); } })) + .WithModule(new ActionModule("/close-browser", HttpVerbs.Get, async ctx => + { + // Redirect to CloseBrowserPage.razor that will close the browser window. + var url = new Uri(absoluteServerAddress, $"/api/Identity/CloseBrowserPage?culture={CultureInfo.CurrentUICulture.Name}").ToString(); + ctx.Redirect(url); + + if (AppPlatform.IsIOS) + { + // CloseBrowserPage.razor's `window.close()` does NOT work on iOS's in app browser. + await MainThread.InvokeOnMainThreadAsync(() => + { +#if iOS + if (UIKit.UIApplication.SharedApplication.KeyWindow?.RootViewController?.PresentedViewController is SafariServices.SFSafariViewController controller) + { + controller.DismissViewController(animated: true, completionHandler: null); + } +#endif + }); + } + else if (AppPlatform.IsAndroid) + { +#if Android + await MainThread.InvokeOnMainThreadAsync(() => + { + var intent = new Android.Content.Intent(Platform.AppContext, typeof(Platforms.Android.MainActivity)); + intent.SetFlags(Android.Content.ActivityFlags.NewTask | Android.Content.ActivityFlags.ClearTop); + Platform.AppContext.StartActivity(intent); + }); +#endif + } + })) .WithModule(new ActionModule("/external-js-runner.html", HttpVerbs.Get, async ctx => { try { + ctx.Response.ContentType = "text/html"; await using var file = Assembly.Load("Boilerplate.Client.Maui").GetManifestResourceStream("Boilerplate.Client.Maui.wwwroot.external-js-runner.html")!; await file.CopyToAsync(ctx.Response.OutputStream, ctx.CancellationToken); } @@ -81,6 +97,7 @@ await MainThread.InvokeOnMainThreadAsync(async () => { try { + ctx.Response.ContentType = "application/javascript"; await using var file = Assembly.Load("Boilerplate.Client.Maui").GetManifestResourceStream("Boilerplate.Client.Maui.wwwroot.scripts.app.js")!; await file.CopyToAsync(ctx.Response.OutputStream, ctx.CancellationToken); } @@ -126,11 +143,4 @@ public async ValueTask DisposeAsync() localHttpServer?.Dispose(); } - /// - /// - /// - public bool ShouldUseForSocialSignIn() - { - return AppPlatform.IsAndroid is false; - } } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Services/MauiWebAuthnService.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Services/MauiWebAuthnService.cs index ead7c54308..8d82b1559c 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Services/MauiWebAuthnService.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Services/MauiWebAuthnService.cs @@ -26,21 +26,7 @@ public override async ValueTask GetWebAuthnCr private static async Task CloseExternalBrowser() { - await MauiExternalJsRunner.RequestToBeSent!.Invoke(JsonSerializer.SerializeToDocument(new { Type = "close" }, JsonSerializerOptions.Web)); - - if (AppPlatform.IsIOS) - { - // SocialSignedInPage.razor's `window.close()` does NOT work on iOS's in app browser. - await MainThread.InvokeOnMainThreadAsync(() => - { -#if iOS - if (UIKit.UIApplication.SharedApplication.KeyWindow?.RootViewController?.PresentedViewController is SafariServices.SFSafariViewController controller) - { - controller.DismissViewController(animated: true, completionHandler: null); - } -#endif - }); - } + _ = MauiExternalJsRunner.RequestToBeSent!.Invoke(JsonSerializer.SerializeToDocument(new { Type = "close" }, JsonSerializerOptions.Web)); } public override async ValueTask CreateWebAuthnCredential(CredentialCreateOptions options) diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/wwwroot/external-js-runner.html b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/wwwroot/external-js-runner.html index ebab2f2fe3..b979768b18 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/wwwroot/external-js-runner.html +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/wwwroot/external-js-runner.html @@ -1,36 +1,40 @@ - + - 🔒 Passwordless login - + Boilerplate + @@ -41,7 +45,7 @@ -->
- +
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 ca60c408f1..bd320f5a8a 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 @@ -35,17 +35,11 @@ public int EnsureStarted() { try { - var url = new Uri(absoluteServerAddress, $"/api/Identity/SocialSignedIn?culture={CultureInfo.CurrentUICulture.Name}").ToString(); - - ctx.Redirect(url); + ctx.Redirect("/close-browser"); _ = Task.Delay(1) .ContinueWith(async _ => { - Application.OpenForms[0]!.Invoke(() => - { - Application.OpenForms[0]!.Activate(); - }); await Routes.OpenUniversalLink(ctx.Request.Url.PathAndQuery, replace: true); }); } @@ -54,10 +48,22 @@ public int EnsureStarted() exceptionHandler.Handle(exp); } })) + .WithModule(new ActionModule("/close-browser", HttpVerbs.Get, async ctx => + { + // Redirect to CloseBrowserPage.razor that will close the browser window. + var url = new Uri(absoluteServerAddress, $"/api/Identity/CloseBrowserPage?culture={CultureInfo.CurrentUICulture.Name}").ToString(); + ctx.Redirect(url); + + Application.OpenForms[0]!.Invoke(() => + { + Application.OpenForms[0]!.Activate(); + }); + })) .WithModule(new ActionModule("/external-js-runner.html", HttpVerbs.Get, async ctx => { try { + ctx.Response.ContentType = "text/html"; await using var fileStream = File.OpenRead("wwwroot/external-js-runner.html"); await fileStream.CopyToAsync(ctx.Response.OutputStream, ctx.CancellationToken); } @@ -70,6 +76,7 @@ public int EnsureStarted() { try { + ctx.Response.ContentType = "application/javascript"; var filePath = Path.Combine(AppContext.BaseDirectory, @"wwwroot\_content\Boilerplate.Client.Core\scripts\app.js"); if (File.Exists(filePath) is false) { @@ -106,12 +113,6 @@ public int EnsureStarted() return port; } - /// - /// - /// - - public bool ShouldUseForSocialSignIn() => true; - public async ValueTask DisposeAsync() { localHttpServer?.Dispose(); diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/Services/WindowsWebAuthnService.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/Services/WindowsWebAuthnService.cs index 7084a6ee58..badb8ca166 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/Services/WindowsWebAuthnService.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/Services/WindowsWebAuthnService.cs @@ -26,12 +26,7 @@ public override async ValueTask GetWebAuthnCr private static async Task CloseExternalBrowser() { - await WindowsExternalJsRunner.RequestToBeSent!.Invoke(JsonSerializer.SerializeToDocument(new { Type = "close" }, JsonSerializerOptions.Web)); - - Application.OpenForms[0]!.Invoke(() => - { - Application.OpenForms[0]!.Activate(); - }); + _ = WindowsExternalJsRunner.RequestToBeSent!.Invoke(JsonSerializer.SerializeToDocument(new { Type = "close" }, JsonSerializerOptions.Web)); } public override async ValueTask CreateWebAuthnCredential(CredentialCreateOptions options) diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Components/SocialSignedInPage.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Components/CloseBrowserPage.razor similarity index 59% rename from src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Components/SocialSignedInPage.razor rename to src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Components/CloseBrowserPage.razor index 7d1fe4f328..6c1e66bd62 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Components/SocialSignedInPage.razor +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Components/CloseBrowserPage.razor @@ -7,13 +7,15 @@ - - @Localizer[nameof(AppStrings.SocialSignedInTitle)] + + @Localizer[nameof(AppStrings.CloseBrowserMessageTitle)]