From 1fc420b7eb77f8486863c4f1639d64f75bccfb30 Mon Sep 17 00:00:00 2001 From: ysmoradi Date: Thu, 5 Dec 2024 15:34:16 +0100 Subject: [PATCH] Add support for pop-up based social sign-in (#9399) --- .../Components/ClientAppCoordinator.cs | 8 ++++++++ .../Components/Layout/JsBridge.razor.cs | 10 ++++++++++ .../IClientCoreServiceCollectionExtensions.cs | 3 +-- .../Boilerplate.Client.Core/Scripts/app.ts | 18 +++++++++++++++++- .../Services/ClientPubSubMessages.cs | 4 ++++ .../DefaultExternalNavigationService.cs | 13 ++++++++++++- .../Services/PubSubService.cs | 2 ++ .../Services/SignalRInfinitiesRetryPolicy.cs | 11 ----------- .../src/Directory.Packages.props | 1 + .../src/Directory.Packages8.props | 1 + .../Boilerplate.Server.Api.csproj | 1 + .../Program.Middlewares.cs | 7 +++++-- .../Program.Middlewares.cs | 9 ++++++--- 13 files changed, 68 insertions(+), 20 deletions(-) delete mode 100644 src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/SignalRInfinitiesRetryPolicy.cs diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/ClientAppCoordinator.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/ClientAppCoordinator.cs index 1fa8cb7bd8..942bedaa81 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/ClientAppCoordinator.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/ClientAppCoordinator.cs @@ -36,6 +36,8 @@ public partial class ClientAppCoordinator : AppComponentBase [AutoInject] private IPushNotificationService pushNotificationService = default!; //#endif + private Action? unsubscribe; + protected override async Task OnInitAsync() { if (AppPlatform.IsBlazorHybrid) @@ -45,6 +47,10 @@ protected override async Task OnInitAsync() if (InPrerenderSession is false) { + unsubscribe = PubSubService.Subscribe(ClientPubSubMessages.NAVIGATE_TO, async (uri) => + { + NavigationManager.NavigateTo(uri!.ToString()!); + }); TelemetryContext.UserAgent = await navigator.GetUserAgent(); TelemetryContext.TimeZone = await jsRuntime.GetTimeZone(); TelemetryContext.Culture = CultureInfo.CurrentCulture.Name; @@ -246,6 +252,8 @@ await storageService.GetItem("Culture") ?? // 2- User settings private List signalROnDisposables = []; protected override async ValueTask DisposeAsync(bool disposing) { + unsubscribe?.Invoke(); + NavigationManager.LocationChanged -= NavigationManager_LocationChanged; AuthManager.AuthenticationStateChanged -= AuthenticationStateChanged; diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/JsBridge.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/JsBridge.razor.cs index 4acf8c6e2e..e39243a534 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/JsBridge.razor.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/JsBridge.razor.cs @@ -27,6 +27,16 @@ public async Task ShowDiagnostic() PubSubService.Publish(ClientPubSubMessages.SHOW_DIAGNOSTIC_MODAL); } + + /// + /// you can add any other method like this to utilize the bridge between js and .net code. + /// + [JSInvokable(nameof(PublishMessage))] + public async Task PublishMessage(string message, string? payload) + { + PubSubService.Publish(message, payload); + } + public void Dispose() { dotnetObj?.Dispose(); diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Extensions/IClientCoreServiceCollectionExtensions.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Extensions/IClientCoreServiceCollectionExtensions.cs index 69530b3e59..b00b65b05a 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Extensions/IClientCoreServiceCollectionExtensions.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Extensions/IClientCoreServiceCollectionExtensions.cs @@ -136,7 +136,6 @@ public static IServiceCollection AddClientCoreProjectServices(this IServiceColle services.AddTypedHttpClients(); //#if (signalR == true) - services.AddSingleton(); services.AddSessioned(sp => { var authManager = sp.GetRequiredService(); @@ -144,7 +143,7 @@ public static IServiceCollection AddClientCoreProjectServices(this IServiceColle var absoluteServerAddressProvider = sp.GetRequiredService(); var hubConnection = new HubConnectionBuilder() - .WithAutomaticReconnect(sp.GetRequiredService()) + .WithStatefulReconnect() .WithUrl(new Uri(absoluteServerAddressProvider.GetAddress(), "app-hub"), options => { options.SkipNegotiation = false; // Required for Azure SignalR. diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Scripts/app.ts b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Scripts/app.ts index 9992da2431..ad0034817b 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Scripts/app.ts +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Scripts/app.ts @@ -5,6 +5,12 @@ class App { public static registerJsBridge(dotnetObj: DotNetObject) { // For additional details, see the JsBridge.cs file. App.jsBridgeObj = dotnetObj; + window.addEventListener('message', e => { + // Enable publishing messages from JavaScript's `window.postMessage` to the C# `PubSubService`. + if (e.data.key === 'PUBLISH_MESSAGE') { + App.jsBridgeObj?.invokeMethodAsync('PublishMessage', e.data.message, e.data.payload); + } + }); } public static showDiagnostic() { @@ -70,7 +76,17 @@ interface DotNetObject { dispose(): void; } -window.addEventListener('load', setCssWindowSizes); +window.addEventListener('load', () => { + setCssWindowSizes(); + if (window.opener != null) { + // The IExternalNavigationService is responsible for opening pages in a new window, + // such as during social sign-in flows. Once the external navigation is complete, + // and the user is redirected back to the newly opened window, + // the following code ensures that the original window is notified of where it should navigate next. + window.opener.postMessage({ key: 'PUBLISH_MESSAGE', message: 'NAVIGATE_TO', payload: window.location.href }); + window.close(); + } +}); window.addEventListener('resize', setCssWindowSizes); function setCssWindowSizes() { diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/ClientPubSubMessages.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/ClientPubSubMessages.cs index 3a7e05b86b..b0414e6ce4 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/ClientPubSubMessages.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/ClientPubSubMessages.cs @@ -22,5 +22,9 @@ public static partial class ClientPubSubMessages public const string UPDATE_IDENTITY_HEADER_BACK_LINK = nameof(UPDATE_IDENTITY_HEADER_BACK_LINK); public const string IDENTITY_HEADER_BACK_LINK_CLICKED = nameof(IDENTITY_HEADER_BACK_LINK_CLICKED); + /// + /// Supposed to be called using JavaScript to navigate between pages without reloading the app. + /// + public const string NAVIGATE_TO = nameof(NAVIGATE_TO); public const string SHOW_DIAGNOSTIC_MODAL = nameof(SHOW_DIAGNOSTIC_MODAL); } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/DefaultExternalNavigationService.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/DefaultExternalNavigationService.cs index f82360f428..57c2db6d29 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/DefaultExternalNavigationService.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/DefaultExternalNavigationService.cs @@ -2,10 +2,21 @@ public partial class DefaultExternalNavigationService : IExternalNavigationService { + [AutoInject] private readonly Window window = default!; [AutoInject] private readonly NavigationManager navigationManager = default!; public async Task NavigateToAsync(string url) { - navigationManager.NavigateTo(url, forceLoad: true, replace: true); + if (AppPlatform.IsBlazorHybrid) + { + navigationManager.NavigateTo(url, forceLoad: true, replace: true); + return; + } + + if (await window.Open(url, "_blank", new WindowFeatures() { Popup = true, Height = 768, Width = 1024 }) is false // Let's try with popup first. + && await window.Open(url, "_blank", new WindowFeatures() { Popup = false }) is false) // Let's try new tab + { + navigationManager.NavigateTo(url, forceLoad: true, replace: true); // If all else fails, let's try to navigate in the same tab. + } } } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/PubSubService.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/PubSubService.cs index 62818332d8..9e914e8ae2 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/PubSubService.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/PubSubService.cs @@ -2,6 +2,8 @@ /// /// Service for Publish/Subscribe pattern. +/// You could publish messages within blazor components, pages outside blazor components (Like MAUI Xaml pages), JavaScript +/// codes using window.postMessage or even from server side using SignalR ( as example. /// public partial class PubSubService { diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/SignalRInfinitiesRetryPolicy.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/SignalRInfinitiesRetryPolicy.cs deleted file mode 100644 index 36a88e7246..0000000000 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/SignalRInfinitiesRetryPolicy.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Microsoft.AspNetCore.SignalR.Client; - -namespace Boilerplate.Client.Core.Services; - -public class SignalRInfinitiesRetryPolicy : IRetryPolicy -{ - public TimeSpan? NextRetryDelay(RetryContext retryContext) - { - return TimeSpan.FromSeconds(1); // It's already handled by HttpMessageHandlers/RetryDelegatingHandler during negotiate http request. - } -} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Directory.Packages.props b/src/Templates/Boilerplate/Bit.Boilerplate/src/Directory.Packages.props index 1c8699815b..1308e45b01 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Directory.Packages.props +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Directory.Packages.props @@ -21,6 +21,7 @@ + diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Directory.Packages8.props b/src/Templates/Boilerplate/Bit.Boilerplate/src/Directory.Packages8.props index 5c9a64fbf5..8b6b2f5374 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Directory.Packages8.props +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Directory.Packages8.props @@ -21,6 +21,7 @@ + diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Boilerplate.Server.Api.csproj b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Boilerplate.Server.Api.csproj index 7e9651eedf..8d6f6853f9 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Boilerplate.Server.Api.csproj +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Boilerplate.Server.Api.csproj @@ -19,6 +19,7 @@ + diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Program.Middlewares.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Program.Middlewares.cs index cd0f59b765..a12e69e9d5 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Program.Middlewares.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Program.Middlewares.cs @@ -45,8 +45,11 @@ private static void ConfigureMiddlewares(this WebApplication app) { app.UseHttpsRedirection(); app.UseResponseCompression(); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + app.UseXContentTypeOptions(); + app.UseXXssProtection(options => options.EnabledWithBlockMode()); + app.UseXfo(options => options.SameOrigin()); } app.UseResponseCaching(); @@ -79,7 +82,7 @@ private static void ConfigureMiddlewares(this WebApplication app) }).WithTags("Test"); //#if (signalR == true) - app.MapHub("/app-hub"); + app.MapHub("/app-hub", options => options.AllowStatefulReconnects = true); //#endif app.MapControllers().RequireAuthorization(); diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Web/Program.Middlewares.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Web/Program.Middlewares.cs index 253540dc08..9bd5bef3f5 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Web/Program.Middlewares.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Web/Program.Middlewares.cs @@ -49,7 +49,7 @@ public static void ConfigureMiddlewares(this WebApplication app) app.UseExceptionHandler("/", createScopeForErrors: true); - if (env.IsDevelopment()) + if (env.IsDevelopment() && false) { app.UseWebAssemblyDebugging(); } @@ -57,8 +57,11 @@ public static void ConfigureMiddlewares(this WebApplication app) { app.UseHttpsRedirection(); app.UseResponseCompression(); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + app.UseXContentTypeOptions(); + app.UseXXssProtection(options => options.EnabledWithBlockMode()); + app.UseXfo(options => options.SameOrigin()); } app.UseResponseCaching(); @@ -140,7 +143,7 @@ public static void ConfigureMiddlewares(this WebApplication app) // and use the Server.Web project solely as a Blazor Server or pre-rendering service provider. throw new InvalidOperationException("Azure SignalR is not supported with Blazor Server and Auto"); } - app.MapHub("/app-hub"); + app.MapHub("/app-hub", options => options.AllowStatefulReconnects = true); //#endif app.MapControllers().RequireAuthorization();