using System.Reflection; using System.Text.Json; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Microsoft.JSInterop; using MoonCore.Blazor.Services; using MoonCore.Blazor.Tailwind.Extensions; using MoonCore.Blazor.Tailwind.Auth; using MoonCore.Extensions; using MoonCore.Helpers; using MoonCore.Plugins; using Moonlight.Client.Implementations; using Moonlight.Client.Interfaces; using Moonlight.Client.Services; using Moonlight.Shared.Misc; using Moonlight.Client.UI; namespace Moonlight.Client; public class Startup { private string[] Args; // Configuration private FrontendConfiguration Configuration; // Logging private ILoggerProvider[] LoggerProviders; private ILoggerFactory LoggerFactory; private ILogger Logger; // WebAssemblyHost private WebAssemblyHostBuilder WebAssemblyHostBuilder; private WebAssemblyHost WebAssemblyHost; // Plugin Loading private PluginLoaderService PluginLoaderService; private ApplicationAssemblyService ApplicationAssemblyService; private IPluginStartup[] PluginStartups; public async Task Run(string[] args, Assembly[]? assemblies = null) { Args = args; // Setup assembly storage ApplicationAssemblyService = new() { AdditionalAssemblies = assemblies ?? [] }; await PrintVersion(); await SetupLogging(); await CreateWebAssemblyHostBuilder(); await LoadConfiguration(); await LoadPlugins(); await InitializePlugins(); await RegisterLogging(); await RegisterBase(); await RegisterAuthentication(); await HookPluginBuild(); await BuildWebAssemblyHost(); await HookPluginConfigure(); await LoadAssets(); await WebAssemblyHost.RunAsync(); } private Task PrintVersion() { // Fancy start console output... yes very fancy :> Console.Write("Running "); var rainbow = new Crayon.Rainbow(0.5); foreach (var c in "Moonlight") { Console.Write( rainbow .Next() .Bold() .Text(c.ToString()) ); } Console.WriteLine(); return Task.CompletedTask; } private async Task LoadConfiguration() { try { using var httpClient = new HttpClient(); httpClient.BaseAddress = new Uri(WebAssemblyHostBuilder.HostEnvironment.BaseAddress); var jsonText = await httpClient.GetStringAsync("frontend.json"); Configuration = JsonSerializer.Deserialize(jsonText, new JsonSerializerOptions() { PropertyNameCaseInsensitive = true })!; WebAssemblyHostBuilder.Services.AddSingleton(Configuration); } catch (Exception e) { Logger.LogCritical("Unable to load configuration. Unable to continue: {e}", e); throw; } } private Task RegisterBase() { WebAssemblyHostBuilder.RootComponents.Add("#app"); WebAssemblyHostBuilder.RootComponents.Add("head::after"); WebAssemblyHostBuilder.Services.AddScoped(_ => new HttpClient { BaseAddress = new Uri(Configuration.ApiUrl) } ); WebAssemblyHostBuilder.Services.AddScoped(sp => { var httpClient = sp.GetRequiredService(); var httpApiClient = new HttpApiClient(httpClient); var localStorageService = sp.GetRequiredService(); httpApiClient.OnConfigureRequest += async request => { var accessToken = await localStorageService.GetString("AccessToken"); if (string.IsNullOrEmpty(accessToken)) return; request.Headers.Add("Authorization", $"Bearer {accessToken}"); }; return httpApiClient; }); WebAssemblyHostBuilder.Services.AddScoped(); WebAssemblyHostBuilder.Services.AddMoonCoreBlazorTailwind(); WebAssemblyHostBuilder.Services.AddScoped(); WebAssemblyHostBuilder.Services.AddScoped(); WebAssemblyHostBuilder.Services.AutoAddServices(); return Task.CompletedTask; } #region Asset Loading private async Task LoadAssets() { var jsRuntime = WebAssemblyHost.Services.GetRequiredService(); foreach (var scriptName in Configuration.Scripts) await jsRuntime.InvokeVoidAsync("moonlight.assets.loadJavascript", scriptName); } #endregion #region Plugins private async Task LoadPlugins() { // Initialize api server plugin loader PluginLoaderService = new PluginLoaderService( LoggerFactory.CreateLogger() ); // Create everything required to stream plugins using var clientForStreaming = new HttpClient(); clientForStreaming.BaseAddress = new Uri(Configuration.HostEnvironment == "ApiServer" ? Configuration.ApiUrl : WebAssemblyHostBuilder.HostEnvironment.BaseAddress ); PluginLoaderService.AddSource(new RemotePluginSource( Configuration, LoggerFactory.CreateLogger(), clientForStreaming )); // Perform assembly loading await PluginLoaderService.Load(); // Add plugin loader service to di for the Router/App.razor ApplicationAssemblyService.PluginAssemblies = PluginLoaderService.PluginAssemblies; WebAssemblyHostBuilder.Services.AddSingleton(ApplicationAssemblyService); } private Task InitializePlugins() { // Define minimal service collection var startupSc = new ServiceCollection(); // Create logging proxy startupSc.AddLogging(builder => { builder.ClearProviders(); builder.AddProviders(LoggerProviders); }); // var startupSp = startupSc.BuildServiceProvider(); // Initialize plugin startups var startups = new List(); var startupType = typeof(IPluginStartup); var assembliesToScan = new List(); assembliesToScan.Add(typeof(Startup).Assembly); assembliesToScan.AddRange(PluginLoaderService.PluginAssemblies); assembliesToScan.AddRange(ApplicationAssemblyService.AdditionalAssemblies); foreach (var pluginAssembly in assembliesToScan) { var startupTypes = pluginAssembly .ExportedTypes .Where(x => !x.IsAbstract && !x.IsInterface && x.IsAssignableTo(startupType)) .ToArray(); foreach (var type in startupTypes) { var startup = ActivatorUtilities.CreateInstance(startupSp, type) as IPluginStartup; if(startup == null) continue; startups.Add(startup); } } PluginStartups = startups.ToArray(); return Task.CompletedTask; } #region Hooks private async Task HookPluginBuild() { foreach (var pluginAppStartup in PluginStartups) { try { await pluginAppStartup.BuildApplication(WebAssemblyHostBuilder); } catch (Exception e) { Logger.LogError( "An error occured while processing 'BuildApp' for '{name}': {e}", pluginAppStartup.GetType().FullName, e ); } } } private async Task HookPluginConfigure() { foreach (var pluginAppStartup in PluginStartups) { try { await pluginAppStartup.ConfigureApplication(WebAssemblyHost); } catch (Exception e) { Logger.LogError( "An error occured while processing 'ConfigureApp' for '{name}': {e}", pluginAppStartup.GetType().FullName, e ); } } } #endregion #endregion #region Logging private Task SetupLogging() { LoggerProviders = LoggerBuildHelper.BuildFromConfiguration(configuration => { configuration.Console.Enable = true; configuration.Console.EnableAnsiMode = true; configuration.FileLogging.Enable = false; }); LoggerFactory = new LoggerFactory(); LoggerFactory.AddProviders(LoggerProviders); Logger = LoggerFactory.CreateLogger(); return Task.CompletedTask; } private Task RegisterLogging() { WebAssemblyHostBuilder.Logging.ClearProviders(); WebAssemblyHostBuilder.Logging.AddProviders(LoggerProviders); return Task.CompletedTask; } #endregion #region Web Application private Task CreateWebAssemblyHostBuilder() { WebAssemblyHostBuilder = WebAssemblyHostBuilder.CreateDefault(Args); return Task.CompletedTask; } private Task BuildWebAssemblyHost() { WebAssemblyHost = WebAssemblyHostBuilder.Build(); return Task.CompletedTask; } #endregion #region Authentication private Task RegisterAuthentication() { WebAssemblyHostBuilder.Services.AddAuthorizationCore(); WebAssemblyHostBuilder.Services.AddCascadingAuthenticationState(); WebAssemblyHostBuilder.Services.AddAuthenticationStateManager(); return Task.CompletedTask; } #endregion }