Skip to content

Commit

Permalink
Merge branch 'bitfoundation:develop' into 7761-dotnet9-rc2
Browse files Browse the repository at this point in the history
  • Loading branch information
ysmoradi authored Oct 29, 2024
2 parents 48a33f7 + 55fa21a commit c2d275a
Show file tree
Hide file tree
Showing 67 changed files with 565 additions and 264 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@
"path": "README.md"
},
{
"path": "Boilerplate.sln"
"path": "Boilerplate.Web.slnf"
}
],
"sources": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@

<Target Name="BuildJavaScript" Inputs="@(TypeScriptFiles);tsconfig.json;package.json" Outputs="wwwroot\scripts\app.js">
<Exec Command="node_modules/.bin/tsc" StandardOutputImportance="high" StandardErrorImportance="high" />
<Exec Condition=" '$(Configuration)' == 'Release' " Command="node_modules/.bin/esbuild wwwroot/scripts/app.js --minify --outfile=wwwroot/scripts/app.js --allow-overwrite" StandardOutputImportance="high" StandardErrorImportance="high" />
<Exec Condition=" '$(Environment)' == 'Production' " Command="node_modules/.bin/esbuild wwwroot/scripts/app.js --minify --outfile=wwwroot/scripts/app.js --allow-overwrite" StandardOutputImportance="high" StandardErrorImportance="high" />
</Target>

<Target Name="BuildCssFiles">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public partial class AppComponentBase : ComponentBase, IAsyncDisposable
private readonly CancellationTokenSource cts = new();
protected CancellationToken CurrentCancellationToken => cts.Token;

protected bool InPrerenderSession => JSRuntime.IsInitialized() is false;
protected bool InPrerenderSession => AppPlatform.IsBlazorHybrid is false && JSRuntime.IsInitialized() is false;

protected sealed override void OnInitialized()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public partial class AppInitializer : AppComponentBase
[AutoInject] private IStorageService storageService = default!;
[AutoInject] private CultureInfoManager cultureInfoManager = default!;
[AutoInject] private NavigationManager navigationManager = default!;
[AutoInject] private Bit.Butil.Console console = default!;

protected async override Task OnInitAsync()
{
Expand All @@ -38,6 +39,8 @@ await storageService.GetItem("Culture") ?? // 2- User settings
}

await SetupBodyClasses();

BrowserConsoleLoggerProvider.SetConsole(console);
}

await base.OnInitAsync();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ private async Task DeleteCategory()

try
{
await categoryController.Delete(deletingCategory.Id, CurrentCancellationToken);
await categoryController.Delete(deletingCategory.Id, deletingCategory.ConcurrencyStamp.ToStampString(), CurrentCancellationToken);

await RefreshData();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ private async Task DeleteProduct()

try
{
await productController.Delete(deletingProduct.Id, CurrentCancellationToken);
await productController.Delete(deletingProduct.Id, deletingProduct.ConcurrencyStamp.ToStampString(), CurrentCancellationToken);

await RefreshData();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
namespace Boilerplate.Client.Core.Components.Pages;
using Microsoft.Extensions.Logging;

namespace Boilerplate.Client.Core.Components.Pages;

public partial class NotAuthorizedPage
{
private ClaimsPrincipal user = default!;

[SupplyParameterFromQuery(Name = "return-url"), Parameter] public string? ReturnUrl { get; set; }

[AutoInject] private ILogger<NotAuthorizedPage> logger = default!;

protected override async Task OnParamsSetAsync()
{
user = (await AuthenticationStateTask).User;
Expand All @@ -25,6 +29,8 @@ protected override async Task OnAfterFirstRenderAsync()
{
await AuthenticationManager.RefreshToken();

logger.LogInformation("Refreshing access token.");

if ((await AuthenticationStateTask).User.IsAuthenticated())
{
if (ReturnUrl is not null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ await Task.Run(async () =>
}
});

if (CultureInfoManager.MultilingualEnabled && forceLoad == false)
if (CultureInfoManager.MultilingualEnabled &&
forceLoad == false &&
(AppPlatform.IsAndroid || AppPlatform.IsIOS))
{
var currentCulture = CultureInfo.CurrentUICulture.Name;
var uri = new Uri(url);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace System;

public static class ByteArrayExtensions
{
public static string ToStampString(this byte[]? source)
{
if (source is null or { Length: 0 })
source = [0, 0, 0, 0, 0, 0, 0, 0];

var base64String = Convert.ToBase64String(source);

return Uri.EscapeDataString(base64String);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

namespace Microsoft.Extensions.DependencyInjection;

public static partial class IServiceCollectionExtensions
public static partial class IClientCoreServiceCollectionExtensions
{
public static IServiceCollection AddClientCoreProjectServices(this IServiceCollection services, IConfiguration configuration)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//+:cnd:noEmit
using System.Reflection;
//#if (notification == true)
using Boilerplate.Shared.Dtos.PushNotification;
//#endif
Expand Down Expand Up @@ -47,7 +48,8 @@ public static bool IsInitialized(this IJSRuntime jsRuntime)
{
"UnsupportedJavaScriptRuntime" => false, // pre-rendering
"RemoteJSRuntime" /* blazor server */ => (bool)type.GetProperty("IsInitialized")!.GetValue(jsRuntime)!,
_ => true // blazor wasm / hybrid
"WebViewJSRuntime" /* blazor hybrid */ => type.GetField("_ipcSender", BindingFlags.NonPublic | BindingFlags.Instance)!.GetValue(jsRuntime) is not null,
_ => true // blazor wasm
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Microsoft.Extensions.Logging;

public static class ILoggingBuilderExtensions
{
public static ILoggingBuilder AddBrowserConsoleLogger(this ILoggingBuilder builder)
{
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, BrowserConsoleLoggerProvider>());

return builder;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public static partial class AppPlatform
public static bool IsAndroid => OperatingSystem.IsAndroid();

[SupportedOSPlatformGuard("ios")]
public static bool IsIOS => OperatingSystem.IsIOS();
public static bool IsIOS => OperatingSystem.IsIOS() && !IsIosOnMacOS;

[SupportedOSPlatformGuard("windows")]
public static bool IsWindows => OperatingSystem.IsWindows();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var access_token = await prerenderStateService.GetValue(() => tokenProvider.GetAccessToken());

if (string.IsNullOrEmpty(access_token) && jsRuntime.IsInitialized())
bool inPrerenderSession = AppPlatform.IsBlazorHybrid is false && jsRuntime.IsInitialized() is false;

if (string.IsNullOrEmpty(access_token) && inPrerenderSession is false)
{
string? refresh_token = await storageService.GetItem("refresh_token");

Expand Down Expand Up @@ -126,7 +128,7 @@ private async Task StoreTokens(TokenResponseDto response, bool? rememberMe = nul
await storageService.SetItem("access_token", response!.AccessToken, rememberMe is true);
await storageService.SetItem("refresh_token", response!.RefreshToken, rememberMe is true);

if (jsRuntime.IsInitialized())
if (AppPlatform.IsBlazorHybrid is false && jsRuntime.IsInitialized())
{
await cookie.Set(new()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//+:cnd:noEmit
using Microsoft.Extensions.Logging;

namespace Boilerplate.Client.Core.Services;

// https://learn.microsoft.com/en-us/aspnet/core/blazor/hybrid/developer-tools

/// <summary>
/// This logger writes to the browser console in blazor hybrid.
/// </summary>
[ProviderAlias("BrowserConsolelogger")]
public partial class BrowserConsoleLoggerProvider : ILoggerProvider, ILogger, IDisposable
{
private static Bit.Butil.Console? console;

public static void SetConsole(Bit.Butil.Console console)
{
if (AppPlatform.IsBlazorHybrid is false)
throw new InvalidOperationException();
BrowserConsoleLoggerProvider.console = console;
}

public string? CategoryName { get; init; }

private static readonly ConcurrentQueue<object> states = new();

public ILogger CreateLogger(string categoryName)
{
return new BrowserConsoleLoggerProvider
{
CategoryName = categoryName
};
}

public IDisposable? BeginScope<TState>(TState state)
where TState : notnull
{
if (state is IDictionary<string, object?> dictionary)
{
dictionary["Category"] = CategoryName;
}

states.Enqueue(state);

return this;
}

public bool IsEnabled(LogLevel logLevel)
{
return AppPlatform.IsBlazorHybrid
&& console is not null;
}

public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
{
if (IsEnabled(logLevel) is false) return;

var message = formatter(state, exception);

var currentState = states.TryDequeue(out var stateFromQueue) ? stateFromQueue : state;

switch (logLevel)
{
case LogLevel.Trace:
case LogLevel.Debug:
console!.Log(message, $"{Environment.NewLine}State:", currentState);
break;
case LogLevel.Information:
console!.Info(message, $"{Environment.NewLine}State:", currentState);
break;
case LogLevel.Warning:
console!.Warn(message, $"{Environment.NewLine}State:", currentState);
break;
case LogLevel.Error:
case LogLevel.Critical:
console!.Error(message, $"{Environment.NewLine}State:", currentState);
break;
case LogLevel.None:
break;
default:
console!.Log(message, $"{Environment.NewLine}State:", currentState);
break;
}
}

public void Dispose()
{
states.TryDequeue(out _);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace Boilerplate.Client.Core.Services.Contracts;
public interface IPushNotificationService
{
string Token { get; set; }
Task<bool> IsNotificationSupported();
Task<DeviceInstallationDto> GetDeviceInstallation();
Task<bool> IsNotificationSupported(CancellationToken cancellationToken);
Task<DeviceInstallationDto> GetDeviceInstallation(CancellationToken cancellationToken);
Task RegisterDevice(CancellationToken cancellationToken);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
//-:cnd:noEmit
using System.Diagnostics;
using System.Text;
using Microsoft.Extensions.Logging;
using System.Runtime.CompilerServices;

Expand Down Expand Up @@ -40,16 +39,6 @@ protected virtual void Handle(Exception exception, Dictionary<string, object> pa

if (isDevEnv)
{
if (AppPlatform.IsBrowser is false)
{
StringBuilder errorInfo = new();
errorInfo.AppendLine(exceptionMessage);
foreach (var item in parameters)
{
errorInfo.AppendLine($"{item.Key}: {item.Value}");
}
_ = Console.Error(errorInfo.ToString());
}
Debugger.Break();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
// Let's update the access token by refreshing it when a refresh token is available.
// Following this procedure, the newly acquired access token may now include the necessary roles or claims.

if (jsRuntime.IsInitialized() is false)
if (AppPlatform.IsBlazorHybrid is false && jsRuntime.IsInitialized() is false)
throw; // We don't have access to refresh_token during pre-rendering.

if (request.RequestUri?.LocalPath?.Contains("api/Identity/Refresh", StringComparison.InvariantCultureIgnoreCase) is true)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,27 @@
using Boilerplate.Shared.Dtos.PushNotification;
using Boilerplate.Shared.Controllers.PushNotification;
using Microsoft.Extensions.Logging;

namespace Boilerplate.Client.Core.Services;

public abstract partial class PushNotificationServiceBase : IPushNotificationService
{
[AutoInject] protected ILogger<PushNotificationServiceBase> Logger = default!;
[AutoInject] protected IPushNotificationController pushNotificationController = default!;

public virtual string Token { get; set; }
public virtual Task<bool> IsNotificationSupported() => Task.FromResult(false);
public abstract Task<DeviceInstallationDto> GetDeviceInstallation();
public virtual Task<bool> IsNotificationSupported(CancellationToken cancellationToken) => Task.FromResult(false);
public abstract Task<DeviceInstallationDto> GetDeviceInstallation(CancellationToken cancellationToken);

public async Task RegisterDevice(CancellationToken cancellationToken)
{
if (await IsNotificationSupported() is false)
return;

using CancellationTokenSource cts = new(TimeSpan.FromSeconds(30));
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, cts.Token);

while (string.IsNullOrEmpty(Token))
if (await IsNotificationSupported(cancellationToken) is false)
{
// After the NotificationsSupported Task completes with a result of true,
// we use FirebaseMessaging.Instance.GetToken and UNUserNotificationCenter.Current.Delegate.
// Those methods are asynchronous and we need to wait for them to complete.
await Task.Delay(TimeSpan.FromSeconds(1), linkedCts.Token);
Logger.LogInformation("Notifications are not supported/allowed on this device.");
return;
}

var deviceInstallation = await GetDeviceInstallation();
var deviceInstallation = await GetDeviceInstallation(cancellationToken);

await pushNotificationController.RegisterDevice(deviceInstallation, cancellationToken);
}
Expand Down
Loading

0 comments on commit c2d275a

Please sign in to comment.