using System.Diagnostics; using System.Reflection; using System.Runtime.InteropServices; using System.Text.Json; using Microsoft.UI.Windowing; using Microsoft.Web.WebView2.Core; using MMoneyCoderSharp.Data; using Windows.Graphics; using Windows.System; using WinRT.Interop; namespace MBN_STOCK_WEBVIEW; public sealed partial class MainWindow : Window { private const string AppHost = "app.mbn.local"; private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web); private bool _webViewReady; public MainWindow() { InitializeComponent(); Root.Loaded += OnRootLoaded; Closed += OnClosed; } private async void OnRootLoaded(object sender, RoutedEventArgs e) { Root.Loaded -= OnRootLoaded; ResizeWindow(); await InitializeWebViewAsync(); } private void ResizeWindow() { try { var windowHandle = WindowNative.GetWindowHandle(this); var windowId = Microsoft.UI.Win32Interop.GetWindowIdFromWindow(windowHandle); AppWindow.GetFromWindowId(windowId).Resize(new SizeInt32(1600, 960)); } catch { // The system-selected size is still usable if sizing is unavailable. } } private async Task InitializeWebViewAsync() { try { var webRoot = Path.Combine(AppContext.BaseDirectory, "Web"); var startPage = Path.Combine(webRoot, "index.html"); if (!File.Exists(startPage)) { throw new FileNotFoundException("The packaged Web UI was not found.", startPage); } await Browser.EnsureCoreWebView2Async(); Browser.CoreWebView2.SetVirtualHostNameToFolderMapping( AppHost, webRoot, CoreWebView2HostResourceAccessKind.DenyCors); Browser.CoreWebView2.Settings.AreDevToolsEnabled = Debugger.IsAttached; Browser.CoreWebView2.Settings.AreDefaultContextMenusEnabled = Debugger.IsAttached; Browser.CoreWebView2.Settings.IsStatusBarEnabled = false; Browser.CoreWebView2.NavigationStarting += OnNavigationStarting; Browser.CoreWebView2.NavigationCompleted += OnNavigationCompleted; Browser.CoreWebView2.HistoryChanged += OnHistoryChanged; Browser.CoreWebView2.NewWindowRequested += OnNewWindowRequested; Browser.CoreWebView2.WebMessageReceived += OnWebMessageReceived; Browser.CoreWebView2.ProcessFailed += OnProcessFailed; _webViewReady = true; Browser.Source = new Uri($"https://{AppHost}/index.html"); } catch (Exception exception) { ShowError("WebView2 초기화에 실패했습니다.", exception.Message); } } private void OnNavigationStarting(object? sender, CoreWebView2NavigationStartingEventArgs args) { if (!Uri.TryCreate(args.Uri, UriKind.Absolute, out var uri)) { args.Cancel = true; return; } if (uri.Host.Equals(AppHost, StringComparison.OrdinalIgnoreCase) || uri.Scheme == "about") { LoadingOverlay.Visibility = Visibility.Visible; return; } args.Cancel = true; _ = OpenExternalUriAsync(uri); } private void OnNavigationCompleted(object? sender, CoreWebView2NavigationCompletedEventArgs args) { LoadingOverlay.Visibility = Visibility.Collapsed; if (!args.IsSuccess) { ShowError("운영 화면을 불러오지 못했습니다.", args.WebErrorStatus.ToString()); } } private void OnHistoryChanged(object? sender, object args) { BackButton.IsEnabled = Browser.CanGoBack; ForwardButton.IsEnabled = Browser.CanGoForward; } private void OnNewWindowRequested(object? sender, CoreWebView2NewWindowRequestedEventArgs args) { args.Handled = true; if (Uri.TryCreate(args.Uri, UriKind.Absolute, out var uri)) { _ = OpenExternalUriAsync(uri); } } private void OnWebMessageReceived(object? sender, CoreWebView2WebMessageReceivedEventArgs args) { try { var request = JsonSerializer.Deserialize(args.WebMessageAsJson, JsonOptions); switch (request?.Type) { case "ready": case "request-app-info": PostAppInfo(); break; case "open-external" when request.Payload.ValueKind == JsonValueKind.Object: if (request.Payload.TryGetProperty("url", out var value) && Uri.TryCreate(value.GetString(), UriKind.Absolute, out var uri)) { _ = OpenExternalUriAsync(uri); } break; case "reload": Browser.Reload(); break; } } catch (JsonException exception) { PostMessage("bridge-error", new { message = exception.Message }); } } private void PostAppInfo() { var version = Assembly.GetExecutingAssembly().GetName().Version?.ToString(3) ?? "1.0.0"; PostMessage("app-info", new { name = "MBN Stock WebView", version, framework = ".NET 8 / WinUI 3", windowsAppSdk = "1.8", architecture = RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant(), package = "MSIX", migratedQueryFiles = 71, databaseConfigured = DataQueryExecutor.IsConfigured }); } private void PostMessage(string type, object payload) { if (!_webViewReady) { return; } Browser.CoreWebView2.PostWebMessageAsJson( JsonSerializer.Serialize(new { type, payload }, JsonOptions)); } private static async Task OpenExternalUriAsync(Uri uri) { if (uri.Scheme is "https" or "http") { await Launcher.LaunchUriAsync(uri); } } private void OnProcessFailed(object? sender, CoreWebView2ProcessFailedEventArgs args) { ShowError("WebView2 프로세스가 종료되었습니다.", args.ProcessFailedKind.ToString()); } private void ShowError(string title, string message) { LoadingOverlay.Visibility = Visibility.Collapsed; ErrorBar.Title = title; ErrorBar.Message = message; ErrorBar.IsOpen = true; } private void OnBackClicked(object sender, RoutedEventArgs e) { if (Browser.CanGoBack) { Browser.GoBack(); } } private void OnForwardClicked(object sender, RoutedEventArgs e) { if (Browser.CanGoForward) { Browser.GoForward(); } } private void OnReloadClicked(object sender, RoutedEventArgs e) { if (_webViewReady) { Browser.Reload(); } } private void OnClosed(object sender, WindowEventArgs args) { if (!_webViewReady) { return; } Browser.CoreWebView2.NavigationStarting -= OnNavigationStarting; Browser.CoreWebView2.NavigationCompleted -= OnNavigationCompleted; Browser.CoreWebView2.HistoryChanged -= OnHistoryChanged; Browser.CoreWebView2.NewWindowRequested -= OnNewWindowRequested; Browser.CoreWebView2.WebMessageReceived -= OnWebMessageReceived; Browser.CoreWebView2.ProcessFailed -= OnProcessFailed; } private sealed record WebRequest(string Type, JsonElement Payload); }