("head::after");
+});
builder.Build().Run();
```
@@ -71,18 +72,18 @@ builder.Build().Run();
When loading external module origins, explicitly trust them via window builder policy:
```csharp
-var app = InfiniFrameBlazorAppBuilder.CreateDefault(windowBuilder: wb => {
+var app = InfiniFrameApplication.CreateBuilder().UseBlazorWebView(config => config.ConfigureWindow(wb => {
wb.AddTrustedOrigin("https://xyz");
// add redirects too if needed (e.g. cdn.jsdelivr.net, unpkg.com, etc.)
-});
+})).Build();
```
If you must allow every origin (not recommended outside local/dev scenarios):
```csharp
-var app = InfiniFrameBlazorAppBuilder.CreateDefault(windowBuilder: wb => {
+var app = InfiniFrameApplication.CreateBuilder().UseBlazorWebView(config => config.ConfigureWindow(wb => {
wb.SetTrustAllOrigins(true);
-});
+})).Build();
```
### Host an ASP.NET Core web app
@@ -90,11 +91,14 @@ var app = InfiniFrameBlazorAppBuilder.CreateDefault(windowBuilder: wb => {
Install: `dotnet add package InfiniLore.InfiniFrame.WebServer`
```csharp
+using InfiniFrame.Application;
using InfiniFrame.WebServer;
-var app = InfiniFrameWebApplication.CreateBuilder(args)
- .Build()
- .UseAutoServerClose();
+var app = InfiniFrameApplication.CreateBuilder(args)
+ .WithWindow("web", static window => window.SetTitle("InfiniFrame"))
+ .UseWebServer("web", web => web.ConfigureWebApplication(application =>
+ application.MapGet("/", () => "Hello from InfiniFrame")))
+ .Build();
app.Run();
```
@@ -195,6 +199,8 @@ npm --prefix docs run build
## Run Linux GUI Tests In Docker
+Linux application lifecycle and WebKit tests run under WSL2/WSLg. Install the SDKs listed in `global.json`, CMake, Ninja, GCC/G++, Clang, `pkg-config`, GTK 3, WebKitGTK 4.1, libnotify, and X11 development packages in the WSL distribution. Run `bash ./docker/linux/run-linux-tests-wslg.sh` from WSL; the native library is built in Linux and is never taken from the Windows output.
+
The full scripts reference lives in docs:
- [Scripts Reference](https://docs.infiniframe.dev/guides/scripts)
diff --git a/docker/README.md b/docker/README.md
index d1394ca8a..0bb1a7699 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -10,7 +10,7 @@ docker/
Dockerfile # Linux container image with all build/test dependencies
docker-compose.yml # Service definitions for tests and examples
entrypoint.sh # Container entrypoint (display setup, build, test dispatch)
- scripts/ # Helper scripts for Docker operations
+ linux/ # Linux Dockerfiles, Compose files, and WSLg wrappers
```
## Quick Start
@@ -84,3 +84,5 @@ The Docker image is based on `mcr.microsoft.com/dotnet/sdk:10.0` and includes:
- Docker Desktop or Docker Engine
- For WSLg: Windows 11 or WSL2 with GUI support
- For host X11: X11 display server on Linux
+
+The WSL2 path requires Windows 11 with WSLg enabled. Run `bash ./docker/linux/run-linux-tests-wslg.sh` from the repository root in WSL. The wrapper exports `DISPLAY`, `WAYLAND_DISPLAY`, `XDG_RUNTIME_DIR`, and `PULSE_SERVER`, mounts the WSLg runtime, and uses the Linux native build inside the container. If the display check fails, confirm `/mnt/wslg` exists and that `echo "$DISPLAY"` is not empty.
diff --git a/docs/docs/cpp/native-cpp-api.md b/docs/docs/cpp/native-cpp-api.md
index 39da0870b..822225094 100644
--- a/docs/docs/cpp/native-cpp-api.md
+++ b/docs/docs/cpp/native-cpp-api.md
@@ -1,13 +1,13 @@
# Native C++ API
-InfiniFrame native API documentation is maintained directly in Docusaurus and sourced from the native headers and comments under `src/InfiniFrame.Native`.
+InfiniFrame native API documentation is maintained directly in Docusaurus and sourced from the native headers and comments under `src/InfiniFrame.NativeBridge/Native`.
## Core headers
- `Core/InfiniFrame.h`: top-level native interop include.
- `Core/InfiniFrameWindow.h`: main native window class and callbacks.
- `Core/InfiniFrameDialog.h`: dialog surface for file/folder/message dialogs.
-- `Core/InfiniFrameInitParams.h`: build-time and startup window parameters.
+- `Core/InfiniFrameWindowInitParams.h`: build-time and startup window parameters.
- `Core/InfiniFrameWindowImpl.h`: shared implementation state for platform windows.
## Shared native types
diff --git a/docs/docs/csharp/async-window-contract-design.md b/docs/docs/csharp/async-window-contract-design.md
index fa11abd4a..5b21d3342 100644
--- a/docs/docs/csharp/async-window-contract-design.md
+++ b/docs/docs/csharp/async-window-contract-design.md
@@ -147,7 +147,7 @@ Do not add async getters or async versions of immediate setters. After the prior
## Native ABI additions
-All current exports and the size-equality-checked `InfiniFrameInitParams` layout remain unchanged. Additive operations use an opaque managed context, a 64-bit operation ID, and exactly-once completion callbacks. General operations report a terminal result, native code, and a borrowed UTF-8 failure string; file-dialog callbacks report borrowed native strings for the callback duration:
+All current exports and the size-equality-checked `InfiniFrameWindowInitParams` layout remain unchanged. Additive operations use an opaque managed context, a 64-bit operation ID, and exactly-once completion callbacks. General operations report a terminal result, native code, and a borrowed UTF-8 failure string; file-dialog callbacks report borrowed native strings for the callback duration:
```cpp
using OperationCompletedCallback =
diff --git a/docs/docs/guides/blazor-webview.md b/docs/docs/guides/blazor-webview.md
index 789914a64..97f8760f4 100644
--- a/docs/docs/guides/blazor-webview.md
+++ b/docs/docs/guides/blazor-webview.md
@@ -1,275 +1,57 @@
# Blazor WebView Guide
-`InfiniLore.InfiniFrame.BlazorWebView` integrates a full Blazor WebAssembly-style application into a native window with no HTTP server required. The Blazor runtime runs entirely in-process.
+`InfiniLore.InfiniFrame.BlazorWebView` hosts a Blazor application in an InfiniFrame native window.
-## Contents
-
-- [How It Works](#how-it-works)
-- [Project Setup](#project-setup)
-- [Program.cs](#programcs)
-- [Available Builder API](#available-builder-api)
-- [Dependency Injection](#dependency-injection)
-- [Custom File Provider](#custom-file-provider)
-- [External JS Modules and Trusted Origins](#external-js-modules-and-trusted-origins)
-- [Error Handling](#error-handling)
-- [HttpClient](#httpclient)
-- [Lifecycle](#lifecycle)
-- [Custom Window Chrome](#custom-window-chrome)
-
-## How It Works
-
-InfiniFrame serves Blazor resources from an internal origin (`app://localhost/`) and handles requests inside the native host. Blazor component files, JavaScript, and CSS are served from an `IFileProvider` backed by your `wwwroot/` folder.
-There is no external ASP.NET server required; all communication happens through the native browser bridge.
-
-`app://localhost/` follows normal browser URL semantics. Query strings and fragments remain in the browser-visible URL, while InfiniFrame removes them only when looking up an embedded resource. For example, navigating to `app://localhost/index.html?mode=desktop#settings` serves `index.html`, leaves the full URL visible, and exposes `#settings` through `window.location.hash`.
-
-Same-origin browser requests to application assets are supported with both `fetch()` and `XMLHttpRequest`. The native engines register `app` as a secure, authority-bearing scheme and allow the `app://localhost` origin. Cross-origin access is not enabled implicitly; add trusted origins through the URI security policy only when the application genuinely needs them.
-
-Platform notes:
-- Windows uses WebView2 and requires custom-scheme registration support (`ICoreWebView2EnvironmentOptions4`) to allow top-level `app://localhost/...` navigation.
-- Linux and macOS use WebKit-based engines and do not depend on WebView2.
-- On Windows, if the WebView2 runtime does not support custom-scheme registration, startup fails fast with a clear error asking for a WebView2 runtime update.
-
-## Project Setup
-
-Your project must use the Razor SDK:
-
-```xml
-
-
- net9.0
- Exe
-
-
-
-
-
-
-```
-
-### wwwroot/index.html
-
-A minimal host page:
-
-```html
-
-
-
-
-
-
-
-
-
- Loading...
-
- An unhandled error has occurred.
-
-
-
-
-```
-
-## Program.cs
+## Setup
```csharp
+using InfiniFrame.Application;
using InfiniFrame.BlazorWebView;
-using Microsoft.Extensions.DependencyInjection;
-
-var builder = InfiniFrameBlazorAppBuilder.CreateDefault(args, w => w
- .SetTitle("My Blazor App")
- .SetSize(1280, 720)
- .Center()
- .SetChromeless(true) // Optional: remove native title bar
-);
-
-// Register services (same as a standard Blazor or ASP.NET Core app)
-builder.Services.AddSingleton();
-builder.Services.AddScoped();
-
-// Register root components (these map to elements in index.html)
-builder.RootComponents.Add("#app");
-builder.RootComponents.Add("head::after");
-
-builder.Build().Run();
-```
-
-`Run()` blocks until the window is closed and then disposes all services.
-
-## Available Builder API
-
-`InfiniFrameBlazorAppBuilder` exposes three properties for configuration:
-
-| Property | Type | Description |
-|------------------|-----------------------------|------------------------------------------------------------------------------|
-| `WindowBuilder` | `IInfiniFrameWindowBuilder` | Fluent window configuration; all options from the generated C# API reference |
-| `Services` | `IServiceCollection` | Standard .NET DI container |
-| `RootComponents` | `RootComponentList` | Maps Blazor components to CSS selectors in index.html |
-
-### Configuring the window separately
-
-```csharp
-var builder = InfiniFrameBlazorAppBuilder.CreateDefault();
-
-builder.WithInfiniFrameWindowBuilder(w => w
- .SetTitle("Configured Later")
- .SetDevToolsEnabled(true)
-);
-```
-
-## Dependency Injection
-
-The following services are automatically registered and available for injection:
-
-| Service | Lifetime | Description |
-|----------------------|-----------|---------------------------------------|
-| `IInfiniFrameWindow` | Singleton | The native window instance |
-| `IInfiniFrameJs` | Scoped | JavaScript interop utilities |
-| `HttpClient` | Scoped | Preconfigured for in-process requests |
-| `Dispatcher` | Singleton | Blazor's component dispatcher |
-
-### Injecting the window in a component
-
-```razor
-@inject IInfiniFrameWindow Window
-
-
-
-
-@code {
- void Minimize() => Window.Invoke(() => { /* window ops must be on UI thread */ });
- void Close() => Window.Close();
-}
-```
-
-## Custom File Provider
-
-By default, files are served from `{AppBaseDirectory}/wwwroot/`.
-You can supply a custom `IFileProvider` for embedded resources, encrypted assets, or virtual file systems:
-
-```csharp
-using Microsoft.Extensions.FileProviders;
-var embeddedProvider = new EmbeddedFileProvider(typeof(Program).Assembly, "MyApp.wwwroot");
+var builder = InfiniFrameApplication.CreateBuilder(args)
+ .WithWindow(window => window
+ .SetTitle("My Blazor App")
+ .SetSize(1280, 720));
-var builder = InfiniFrameBlazorAppBuilder.CreateDefault(
- fileProvider: embeddedProvider,
- args: args
-);
-```
-
-## External JS Modules and Trusted Origins
-
-If your app imports scripts from external origins (for example `import ... from "https://cdn.example/..."`), keep `WebSecurity` enabled and explicitly trust those origins.
-
-```csharp
-var app = InfiniFrameBlazorAppBuilder.CreateDefault(windowBuilder: wb => {
- wb.AddTrustedOrigin("https://xyz");
- // add redirects too if needed (e.g. cdn.jsdelivr.net, unpkg.com, etc.)
+builder.UseBlazorWebView(configuration => {
+ configuration.RootComponents.Add("app");
});
-```
-
-For multiple hosts:
-```csharp
-var app = InfiniFrameBlazorAppBuilder.CreateDefault(windowBuilder: wb => {
- wb.AddTrustedOrigin("https://xyz");
- wb.AddTrustedOrigin("https://cdn.jsdelivr.net");
- wb.AddTrustedOrigin("https://unpkg.com");
-});
+await using InfiniFrameApplication app = builder.Build();
+await app.RunAsync();
```
-To disable origin checks entirely (not recommended for production), opt in explicitly:
+Configure window features before registering the integration with `WithWindow` or `WithWindow(id, ...)`.
+`ConfigureWindow` remains available for integration-specific configuration.
```csharp
-var app = InfiniFrameBlazorAppBuilder.CreateDefault(windowBuilder: wb => {
- wb.SetTrustAllOrigins(true);
+builder.WithWindow(window => window.SetChromeless(true));
+builder.UseBlazorWebView(configuration => {
+ configuration.ConfigureWindow(window => window.SetDevToolsEnabled(true));
+ configuration.RootComponents.Add("app");
});
```
-Do not use `.SetWebSecurityEnabled(false)` as a workaround for this scenario.
+The current manager supports one target window per Blazor WebView integration. Use separate application integrations when independent window managers are required.
-## Error Handling
+## Services And Components
-Unhandled exceptions in the process are caught automatically and shown as a native message dialog:
-
-```
-Fatal exception
-System.NullReferenceException: Object reference not set...
-```
-
-To customize error handling, register a handler before `Build()`:
+Register application services on `builder.Services` and root components on the integration configuration:
```csharp
-AppDomain.CurrentDomain.UnhandledException += (_, e) => {
- // Custom logging or reporting
-};
+builder.Services.AddSingleton();
+builder.UseBlazorWebView(configuration => {
+ configuration.RootComponents.Add("app");
+ configuration.RootComponents.Add("head::after");
+});
```
-## HttpClient
-
-An `HttpClient` is registered automatically with `BaseAddress` set to the internal app base URI.
-This lets you make in-process requests to your static assets or call external APIs:
+`IInfiniFrameWindow`, `IInfiniFrameJs`, `HttpClient`, and the Blazor dispatcher are registered by the integration where applicable.
-```razor
-@inject HttpClient Http
+## File Providers And Security
-@code {
- protected override async Task OnInitializedAsync() {
- var data = await Http.GetFromJsonAsync("data/mydata.json");
- }
-}
-```
+The integration serves static assets from the application `wwwroot` and generated static-web-assets locations. Use the configuration APIs for custom file providers and explicitly trust external origins when needed. Keep WebSecurity enabled unless disabling it is an intentional development-only decision.
## Lifecycle
-```
-InfiniFrameBlazorAppBuilder.CreateDefault()
- ↓
-Configure Services & RootComponents
- ↓
-.Build() ← Registers the custom scheme, creates the window
- ↓
-.Run() ← Starts the Blazor runtime, blocks until window closes
- ↓
-DisposeAsync() ← Disposes all services
-```
-
-## Debugging workflow
-
-Use devtools and remote debugging separately:
-
-```csharp
-var builder = InfiniFrameBlazorAppBuilder.CreateDefault(args, w => w
- .SetDevToolsEnabled(true)
- .SetWebInspectorEnabled(true) // macOS 13.3+ Safari Web Inspector attachability
- .SetRemoteDebuggingPort(9222) // Windows and Linux, startup-only
-);
-
-var app = builder.Build();
-
-if (app.Window.Debug.TryGetRemoteDebuggingEndpoint(out Uri? endpoint))
- Console.WriteLine($"Remote debug endpoint: {endpoint}");
-
-app.Run();
-```
-
-- `SetDevToolsEnabled(true)` controls local inspector UI.
-- `SetWebInspectorEnabled(true)` controls WKWebView Safari Web Inspector attachability on macOS 13.3+.
-- `SetRemoteDebuggingPort(int? port)` controls TCP endpoint availability (`1..65535`, `0/null` disables).
-- Linux inspector endpoint uses WebKitGTK inspector server (`http://127.0.0.1:/`).
-- On Linux, WebKit requires developer extras for remote inspector and keeps them enabled while remote debugging is active.
-- On Linux, inspector server configuration is process-scoped (shared across windows in the same process).
-- On unsupported platforms (macOS), enabling remote debugging throws `PlatformNotSupportedException`.
-- On unsupported platforms (Windows/Linux, or macOS below 13.3), enabling web inspector mode throws `PlatformNotSupportedException`.
-
-## Custom Window Chrome
-
-Combine with `InfiniLore.InfiniFrame.Blazor` for a fully custom title bar.
-
-See the [Custom Window Chrome Guide](custom-window-chrome.md) for details. For JavaScript interop and built-in message handlers, see the [JavaScript Interop Guide](javascript-interop.md).
-
-## Examples
-
-- `InfiniFrameExample.BlazorWebView` (`examples/InfiniFrameExample.BlazorWebView`) - minimal Blazor app with window configuration and Serilog
-- `InfiniFrameExample.BlazorWebView.MultiWindowSample` (`examples/InfiniFrameExample.BlazorWebView.MultiWindowSample`) - multiple windows each hosting a different Blazor component
+`Run` or `RunAsync` starts the native application and the Blazor WebView. Closing the application disposes the WebView manager, message pump, windows, and registered services.
diff --git a/docs/docs/guides/core-window.md b/docs/docs/guides/core-window.md
index 239d47e54..2c4436a6a 100644
--- a/docs/docs/guides/core-window.md
+++ b/docs/docs/guides/core-window.md
@@ -33,7 +33,7 @@ The returned `IInfiniFrameWindow` gives you full control over the window at runt
## Single-File Native Packaging
-When your app is published as a single-file executable with embedded InfiniFrame native binaries, call `InfiniFrameSingleFileBootstrap.Initialize()` before creating any windows.
+When your app is published as a single-file executable with embedded InfiniFrame native binaries, call `InfiniFrameSingleFile.Initialize()` before creating any windows.
```csharp
using InfiniFrame;
@@ -41,7 +41,7 @@ using InfiniFrame;
public static class Program {
[STAThread]
public static void Main(string[] args) {
- InfiniFrameSingleFileBootstrap.Initialize();
+ InfiniFrameSingleFile.Initialize();
var window = InfiniFrameWindowBuilder.Create()
.SetTitle("My App")
diff --git a/docs/docs/guides/getting-started.md b/docs/docs/guides/getting-started.md
index b58ce4ce2..fc8ff5e1e 100644
--- a/docs/docs/guides/getting-started.md
+++ b/docs/docs/guides/getting-started.md
@@ -21,7 +21,7 @@ This guide walks you through installing InfiniFrame and creating your first nati
| Platform | Requirement |
|----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Windows | [WebView2 Runtime](https://developer.microsoft.com/en-us/microsoft-edge/webview2/); pre-installed on Windows 11 and available as a redistributable for Windows 10 |
-| Linux | `webkit2gtk-4.0` and `libgtk-3-dev` installed via your package manager |
+| Linux | WebKitGTK 4.1, GTK 3, libnotify, X11 development headers, `pkg-config`, and a WSLg/X11 display for GUI tests |
| macOS | macOS 10.15 Catalina or later (WKWebView is built into the OS) |
## Choose Your Integration
@@ -74,7 +74,7 @@ using InfiniFrame;
public static class Program {
[STAThread]
public static void Main(string[] args) {
- InfiniFrameSingleFileBootstrap.Initialize();
+ InfiniFrameSingleFile.Initialize();
var window = InfiniFrameWindowBuilder.Create()
.SetTitle("Hello, InfiniFrame")
@@ -124,21 +124,24 @@ A minimal `index.html`:
### Program.cs
```csharp
+using InfiniFrame.Application;
using InfiniFrame.BlazorWebView;
using Microsoft.Extensions.DependencyInjection;
-var builder = InfiniFrameBlazorAppBuilder.CreateDefault(args, w => w
- .SetTitle("My Blazor App")
- .SetSize(1280, 720)
- .Center()
-);
+var builder = InfiniFrameApplication.CreateBuilder(args)
+ .WithWindow(window => window
+ .SetTitle("My Blazor App")
+ .SetSize(1280, 720)
+ .Center());
// Register your services
builder.Services.AddSingleton();
// Register root Blazor components
-builder.RootComponents.Add("#app");
-builder.RootComponents.Add("head::after");
+builder.UseBlazorWebView(configuration => {
+ configuration.RootComponents.Add("app");
+ configuration.RootComponents.Add("head::after");
+});
builder.Build().Run();
```
@@ -166,21 +169,19 @@ dotnet add package InfiniLore.InfiniFrame.WebServer
### Program.cs
```csharp
+using InfiniFrame.Application;
using InfiniFrame.WebServer;
-var app = InfiniFrameWebApplication.CreateBuilder(args)
- .Build()
- .UseAutoServerClose();
-
-// Configure the ASP.NET Core pipeline on app.WebApp
-app.WebApp.UseRouting();
-app.WebApp.MapGet("/", () => "Hello from InfiniFrame");
+var app = InfiniFrameApplication.CreateBuilder(args)
+ .WithWindow("web", static _ => { })
+ .UseWebServer("web", web => web.ConfigureWebApplication(application =>
+ application.MapGet("/", () => "Hello from InfiniFrame")))
+ .Build();
app.Run();
```
-The start URL is automatically read from `ASPNETCORE_URLS` or the `urls` configuration key.
-`UseAutoServerClose()` ensures the server shuts down gracefully when the window is closed.
+The start URL is resolved from the server's bound address after startup, and the server shuts down with the InfiniFrame application.
## Next Steps
diff --git a/docs/docs/guides/lifecycle-feature.md b/docs/docs/guides/lifecycle-feature.md
index 3ba2d3595..cfa73fcd6 100644
--- a/docs/docs/guides/lifecycle-feature.md
+++ b/docs/docs/guides/lifecycle-feature.md
@@ -95,7 +95,9 @@ Top-level statements cannot carry `[STAThread]` so use an explicit `static void
`[STAThread]` is silently ignored on `async Task Main`. The async continuation runs on thread pool threads (MTA). Never use `async Task Main` as the entry point for an InfiniFrame application.
:::
-**Linux does not have this restriction** because GTK has no COM apartment model. The native constructor calls `gtk_init()` itself and implicitly claims whichever thread calls `Build()` as the GTK main thread.
+Linux has no COM apartment restriction, but GTK/WebKit still have one owner thread. `InfiniFrameApplication.Register()` initializes GTK, WebKit, libnotify, and the application GTK context before any registered window is built. Window operations are marshalled to that owner thread; `Run()` and `RunAsync()` enter the application-owned lifecycle and return only after the final window's deferred WebKit teardown has drained.
+
+Do not initialize GTK from a window or call the process-level Linux shutdown export in application code. Dispose the application after its windows have closed so the application can drain GLib callbacks. The GTK/WebKit owner is process-scoped and is reused by later applications in the same process; its final cleanup is registered for process exit because WebKitGTK does not safely support tearing down and reinitializing its worker runtime between application instances.
## Cross-Thread Invocation
diff --git a/docs/docs/guides/macos-application-lifecycle.md b/docs/docs/guides/macos-application-lifecycle.md
new file mode 100644
index 000000000..f661f1158
--- /dev/null
+++ b/docs/docs/guides/macos-application-lifecycle.md
@@ -0,0 +1,61 @@
+# macOS Application Lifecycle
+
+InfiniFrame owns AppKit and WebKit initialization through `InfiniFrameApplication`.
+The application must be registered before its first window is constructed. Every
+window receives the native application handle and is registered with the same
+application owner.
+
+## Prerequisites
+
+- macOS 12.0 or newer
+- Xcode with the matching macOS 12.0 or newer SDK
+- CMake 4.0 or newer and an Objective-C++ capable AppleClang toolchain
+- An Xcode or Ninja CMake generator
+- AppKit and WebKit available in the target macOS SDK and runtime
+
+The native target enables Objective-C++ and defaults `CMAKE_OSX_DEPLOYMENT_TARGET`
+to `12.0`. Use `-DCMAKE_OSX_ARCHITECTURES=x86_64` or `arm64` when building a
+single architecture.
+
+## Ownership and Threading
+
+`InfiniFrameApplication.Register` initializes the process `NSApplication`, its
+delegate, menu, activation policy, and launch state. Window construction and all
+AppKit/WKWebView operations are dispatched to the AppKit main thread.
+
+`Run` owns the application event-loop boundary. `RunAsync` may be started from a
+thread-pool continuation; native execution schedules AppKit work on the process
+main thread and waits for the tracked logical windows to close. Closing one
+window removes only that window from the live set. The final live window stops
+the application loop, while the native window object remains registered until
+deferred destruction has completed.
+
+Shutdown is a drain request. Callback routes are detached before WebKit hosts are
+returned to the bounded pool, and one additional main-queue turn protects against
+deletion while an AppKit or reverse P/Invoke callback is unwinding. Application
+disposal drains the pooled hosts on the AppKit thread before releasing the native
+application handle.
+
+## Process-Scoped Runtime
+
+`NSApplication` and WebKit's process infrastructure are process-scoped on macOS.
+InfiniFrame therefore does not attempt to terminate and reconstruct AppKit in the
+same process. Sequential `InfiniFrameApplication` instances are supported after
+the previous instance has drained its windows and pooled hosts; the shared
+`NSApplication` delegate remains installed for the process lifetime.
+
+## CI Verification
+
+Windows and WSL can perform managed builds, ABI/header checks, and static native
+checks, but cannot execute AppKit or WKWebView. Run genuine macOS verification in
+the repository macOS workflow or on a self-hosted Mac:
+
+```sh
+dotnet restore InfiniFrame.GitHubActions.Testing.slnf -p:NativeArch=arm64
+dotnet build InfiniFrame.GitHubActions.Testing.slnf --configuration Release --no-restore -p:NativeArch=arm64
+dotnet test --solution InfiniFrame.GitHubActions.Testing.slnf --configuration Release --no-build --no-restore --framework net10.0 -p:NativeArch=arm64
+```
+
+The macOS workflow also builds x64 and arm64 native artifacts and runs the full
+macOS test matrix. No Windows-built native binary is valid for macOS runtime
+verification.
diff --git a/docs/docs/guides/pack-tool.md b/docs/docs/guides/pack-tool.md
index 2fe4ef038..844c30095 100644
--- a/docs/docs/guides/pack-tool.md
+++ b/docs/docs/guides/pack-tool.md
@@ -30,7 +30,7 @@ Compared to a regular `dotnet publish`, the target additionally:
- Removes unpacked sidecar files from the final publish directory
- Performs a two-pass publish to ensure all content is available before embedding
-Because native files are embedded as resources, your app must initialize the runtime resolver at startup with `InfiniFrameSingleFileBootstrap.Initialize()`.
+Because native files are embedded as resources, your app must initialize the runtime resolver at startup with `InfiniFrameSingleFile.Initialize()`.
## How It Works
@@ -147,7 +147,7 @@ using InfiniFrame;
public static class Program {
[STAThread]
public static void Main(string[] args) {
- InfiniFrameSingleFileBootstrap.Initialize();
+ InfiniFrameSingleFile.Initialize();
var window = InfiniFrameWindowBuilder.Create()
.SetTitle("My App")
@@ -163,7 +163,7 @@ public static class Program {
Why this is required:
- The publish target embeds `InfiniFrame.Native` and platform loader files (`WebView2Loader.dll` on Windows) as resources.
-- `InfiniFrameSingleFileBootstrap.Initialize()` extracts them to a temporary RID-specific folder and registers a native resolver so P/Invoke can load them.
+- `InfiniFrameSingleFile.Initialize()` extracts them to a temporary RID-specific folder and registers a native resolver so P/Invoke can load them.
Alternatively, use the higher-level `InfiniFrameSingleFile.Initialize()` helper which also configures embedded static web assets for Blazor apps:
diff --git a/docs/docs/guides/trim-aot-compatibility.md b/docs/docs/guides/trim-aot-compatibility.md
index 4ec08e727..abf018a84 100644
--- a/docs/docs/guides/trim-aot-compatibility.md
+++ b/docs/docs/guides/trim-aot-compatibility.md
@@ -6,9 +6,10 @@ InfiniFrame includes CI validation lanes for trimming and NativeAOT compatibilit
- The public APIs that rely on runtime reflection or dynamic code generation are explicitly annotated with `RequiresUnreferencedCode` and/or `RequiresDynamicCode`.
- Trim/AOT compatibility checks run in CI and must pass before release workflows continue.
-- `InfiniFrame.SingleFile` is validated with a NativeAOT smoke publish using:
+- The core `InfiniFrame` package is validated with a NativeAOT smoke publish using:
- `PublishTrimmed=true`
- `PublishAot=true`
+- The `InfiniFrame.SingleFile` package and MSBuild targets are validated separately by package/target tests; they are not included in the consumer smoke publish.
## Consumer Guidance
@@ -19,4 +20,4 @@ InfiniFrame includes CI validation lanes for trimming and NativeAOT compatibilit
dotnet publish -c Release -r -p:PublishTrimmed=true -p:PublishAot=true
```
-- If your app uses framework features that depend on reflection (configuration binding, runtime component activation, etc.), account for their requirements explicitly in your trimming strategy.
\ No newline at end of file
+- If your app uses framework features that depend on reflection (configuration binding, runtime component activation, etc.), account for their requirements explicitly in your trimming strategy.
diff --git a/docs/docs/guides/web-server.md b/docs/docs/guides/web-server.md
index a8917b614..ca7c64481 100644
--- a/docs/docs/guides/web-server.md
+++ b/docs/docs/guides/web-server.md
@@ -1,28 +1,6 @@
# Web Server Guide
-`InfiniLore.InfiniFrame.WebServer` runs a standard ASP.NET Core web application in a background thread while opening a native window pointed at it.
-This approach gives you the full ASP.NET Core pipeline (middleware, controllers, SignalR, minimal APIs, Blazor Server) without any browser overhead.
-
-For cross-thread dispatch from ASP.NET Core to the window thread, see the [Invoke feature](invoke-feature.md). For an overview of the feature system, see [Window Features Architecture](window-features-architecture.md).
-
-## Contents
-
-- [How It Works](#how-it-works)
-- [Installation](#installation)
-- [Minimal Setup](#minimal-setup)
-- [Builder API](#builder-api)
-- [Start URL](#start-url)
-- [Accessing the Window from ASP.NET Core](#accessing-the-window-from-aspnet-core)
-- [Graceful Shutdown](#graceful-shutdown)
-- [Example: Blazor Server](#example-blazor-server)
-- [Static Web Assets](#static-web-assets)
-- [Thread Model](#thread-model)
-
-## How It Works
-
-- The ASP.NET Core server starts on a background thread.
-- A native window opens and navigates to the server's URL.
-- Both shut down together when the window is closed (with `UseAutoServerClose()`).
+`InfiniLore.InfiniFrame.WebServer` hosts an ASP.NET Core application alongside native InfiniFrame windows.
## Installation
@@ -33,168 +11,71 @@ dotnet add package InfiniLore.InfiniFrame.WebServer
## Minimal Setup
```csharp
+using InfiniFrame.Application;
using InfiniFrame.WebServer;
-var app = InfiniFrameWebApplication.CreateBuilder(args)
- .Build()
- .UseAutoServerClose();
-
-app.WebApp.MapGet("/", () => "Hello from InfiniFrame");
+var app = InfiniFrameApplication.CreateBuilder(args)
+ .WithWindow("web", window => window.SetTitle("My Desktop App"))
+ .UseWebServer("web", web => web.ConfigureWebApplication(application =>
+ application.MapGet("/", () => "Hello from InfiniFrame")))
+ .Build();
app.Run();
```
-`app.Run()` starts the web server in the background, opens the window, and blocks until the window is closed.
-
-## Builder API
+`Run()` starts the server, opens the window, and stops the server during application shutdown.
-`InfiniFrameWebApplication.CreateBuilder(args)` returns an `InfiniFrameWebApplicationBuilder` with two properties:
+## Configuration
-| Property | Type | Description |
-|----------|----------------------------|----------------------------------------------------------------------|
-| `WebApp` | `WebApplicationBuilder` | Standard ASP.NET Core builder; add services, configure Kestrel, etc. |
-| `Window` | `InfiniFrameWindowBuilder` | Fluent window configuration |
-
-### Configuring the window
+`InfiniFrameWebServerConfiguration` exposes `WebHost` for Kestrel configuration and
+`ConfigureWebApplication` for configuring the built `WebApplication`:
```csharp
-var builder = InfiniFrameWebApplication.CreateBuilder(args);
-
-builder.Window
- .SetTitle("My Desktop App")
- .SetSize(1280, 720)
- .Center()
- .SetDevToolsEnabled(true)
- .SetRemoteDebuggingPort(9222); // Windows and Linux
-
-builder.WebApp.Services.AddControllers();
-builder.WebApp.Services.AddSignalR();
-
-var app = builder.Build().UseAutoServerClose();
-app.WebApp.MapControllers();
-app.WebApp.MapHub("/hub");
-
-app.Run();
-```
-
-Remote debugging notes:
-- `SetRemoteDebuggingPort(...)` is startup-only and validates `1..65535` (`0/null` disables).
-- Linux and Windows are supported. On unsupported platforms (macOS), enabling it throws `PlatformNotSupportedException`.
-- Use `app.Window.Debug.TryGetRemoteDebuggingEndpoint(out Uri? endpoint)` after startup to retrieve the endpoint when available.
-
-## Start URL
-
-The window's start URL is automatically resolved from configuration in this priority order:
-
-1. `ASPNETCORE_URLS` environment variable
-2. `urls` configuration key (e.g. in `appsettings.json`)
-3. Manual override via `builder.Window.SetStartPageUrl(...)`
+var builder = InfiniFrameApplication.CreateBuilder(args)
+ .WithWindow("web", window => window
+ .SetTitle("Web App")
+ .SetSize(1280, 720));
+
+builder.UseWebServer("web", web => {
+ web.WebHost.UseUrls("http://127.0.0.1:5055");
+ web.ConfigureWebApplication(application => {
+ application.UseRouting();
+ application.MapGet("/health", () => Results.Ok());
+ });
+});
-```json
-{
- "urls": "http://localhost:5200"
-}
+builder.Build().Run();
```
-If multiple URLs are configured (e.g. `"http://localhost:5200;https://localhost:7200"`), the first one is used as the window's start URL.
+The server's bound address is resolved after startup. Wildcard bindings are converted to a loopback URL before navigation and origin trust are configured.
-## Accessing the Window from ASP.NET Core
+## Dependency Injection
-`IInfiniFrameWindow` and `IInfiniFrameWindowBuilder` are registered in the web app's DI container:
+Application services are copied into the ASP.NET Core service collection. Window APIs can therefore be injected into handlers:
```csharp
-app.WebApp.MapGet("/close", (IInfiniFrameWindow window) => {
+application.MapGet("/close", (IInfiniFrameWindow window) => {
window.Close();
return Results.Ok();
});
```
-```csharp
-public class MyController(IInfiniFrameWindow window) : ControllerBase {
- [HttpGet("minimize")]
- public IActionResult Minimize() {
- window.Invoke(() => { /* minimize logic */ });
- return Ok();
- }
-}
-```
-
-> **Note:** Window operations that affect the native UI must be marshalled to the window thread using `window.Invoke(...)`.
-
-## Graceful Shutdown
-
-### UseAutoServerClose
-
-Automatically stops the web server when the window is closed or a close is requested:
-
-```csharp
-var app = builder.Build().UseAutoServerClose();
-```
-
-Internally this registers handlers on both `WindowClosing` and `WindowClosingRequested` that call `WebApp.StopAsync()` in a background task, so the UI thread is never blocked.
-
-### Manual shutdown
-
-```csharp
-app.Stop(); // Stops the web server and closes the window
-```
-
-```csharp
-await app.WebApp.StopAsync(); // Stop server only
-app.Window.Close(); // Then close window
-```
-
-## Example: Blazor Server
+## Blazor Server
```csharp
+using InfiniFrame.Application;
using InfiniFrame.WebServer;
-var builder = InfiniFrameWebApplication.CreateBuilder(args);
-
-builder.Window
- .SetTitle("Blazor Server App")
- .SetSize(1280, 720)
- .Center();
+var builder = InfiniFrameApplication.CreateBuilder(args)
+ .WithWindow(window => window.SetTitle("Blazor Server"));
-builder.WebApp.Services.AddRazorComponents()
+builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
-var app = builder.Build().UseAutoServerClose();
+builder.UseWebServer(web => web.ConfigureWebApplication(application =>
+ application.MapRazorComponents().AddInteractiveServerRenderMode()));
-app.WebApp.UseStaticFiles();
-app.WebApp.UseAntiforgery();
-app.WebApp.MapRazorComponents()
- .AddInteractiveServerRenderMode();
-
-app.Run();
+builder.Build().Run();
```
-## Static Web Assets
-
-`UseStaticWebAssets()` is called automatically during builder initialization, so static files from Razor class libraries are served correctly without additional configuration.
-
-`UseDefaultFiles()` is also applied during `Build()`, which causes requests to `/` to serve `wwwroot/index.html` if it exists.
-
-## Thread Model
-
-| Thread | Runs |
-|-------------------|---------------------------|
-| Main thread | Native window (UI thread) |
-| Background thread | ASP.NET Core / Kestrel |
-
-All window API calls from ASP.NET Core handlers must use `window.Invoke(...)` to marshal to the window thread.
-Web server calls from window event handlers can be made directly since ASP.NET Core is thread-safe.
-
-> **Windows:** The main thread must be STA. Add `[STAThread]` to your `Main` method and use an explicit `static void Main()`. Top-level statements and `async Task Main` do not support STA correctly.
-
-## Examples
-
-- `InfiniFrameExample.WebApp.Blazor` (`examples/InfiniFrameExample.WebApp.Blazor`) - Blazor Server with InteractiveServerComponents, HttpClient factory, and InfiniFrameJs
-- `InfiniFrameExample.WebApp.React` (`examples/InfiniFrameExample.WebApp.React`) - React frontend with custom scheme handler and two-way messaging
-- `InfiniFrameExample.WebApp.Vue` (`examples/InfiniFrameExample.WebApp.Vue`) - Vue.js frontend with all built-in JS message handlers
-
-## See Also
-
-- [Invoke Feature](invoke-feature.md) Cross-thread dispatch to the window's native thread
-- [Window Features Architecture](window-features-architecture.md) How the feature system works
-- [Core Window Guide](core-window.md) Builder API and feature overview
+For static assets, configure the appropriate ASP.NET Core static-file or static-asset middleware in `ConfigureWebApplication`.
diff --git a/docs/docs/guides/window-features-architecture.md b/docs/docs/guides/window-features-architecture.md
index 21beb5e81..9d7535a3d 100644
--- a/docs/docs/guides/window-features-architecture.md
+++ b/docs/docs/guides/window-features-architecture.md
@@ -37,7 +37,7 @@ builder.Features.Size.SetSize(1280, 720); // Direct feature access
builder.SetSize(1280, 720); // Extension method equivalent
```
-Builder features apply their settings to `InfiniFrameNativeParameters`, which the native layer reads during window creation. After `Build()`, builder-only settings cannot be changed.
+Builder features apply their settings to `InfiniFrameNativeWindowParameters`, which the native layer reads during window creation. After `Build()`, builder-only settings cannot be changed.
### Runtime Feature (`IInfiniFrameWindowFeature`)
@@ -134,7 +134,7 @@ builder.Features.InstanceArbitration // IInstanceArbitrationInfiniFrameWindowBui
## How Features Are Wired
-1. **Builder phase**: Each builder feature implements `IInfiniFrameWindowBuilderFeature` with an `ApplyToNativeParameters(ref InfiniFrameNativeParameters)` method. The builder collects all settings into a native parameters struct.
+1. **Builder phase**: Each builder feature implements `IInfiniFrameWindowBuilderFeature` with an `ApplyToNativeParameters(ref InfiniFrameNativeWindowParameters)` method. The builder collects all settings into a native parameters struct.
2. **Build**: `InfiniFrameWindowBuilder.Build()` creates the window, then `InfiniFrameWindowFeaturesFactory` creates all runtime feature instances from the DI container, passing the window and the original builder.
diff --git a/docs/docs/migration/photino-breaking-changes.md b/docs/docs/migration/photino-breaking-changes.md
index 5d3bad0ae..66fd25cc4 100644
--- a/docs/docs/migration/photino-breaking-changes.md
+++ b/docs/docs/migration/photino-breaking-changes.md
@@ -32,7 +32,7 @@ For detailed documentation on the new feature-based API, see:
| C# namespace | `Photino.NET` | `InfiniFrame` |
| Native DLL | `Photino.Native` | `InfiniFrame.Native` (internal) |
| C++ class | `Photino` | `InfiniFrameWindow` |
-| C++ init params | `PhotinoInitParams` | `InfiniFrameInitParams` |
+| C++ init params | `PhotinoInitParams` | `InfiniFrameWindowInitParams` |
| Exported function prefix | `Photino_` | `InfiniFrameNative_` |
| Default window title | `"Photino"` | `"InfiniFrame"` |
| Default user agent | `"Photino WebView"` | `"InfiniFrame WebView"` |
@@ -250,19 +250,22 @@ For BlazorWebView, InfiniFrame serves content from `app://localhost/` and valida
The preferred migration pattern is to keep web security on and explicitly trust only the origins you need:
```csharp
-var app = InfiniFrameBlazorAppBuilder.CreateDefault(windowBuilder: wb => {
- wb.AddTrustedOrigin("https://xyz");
- wb.AddTrustedOrigin("https://cdn.jsdelivr.net");
- wb.AddTrustedOrigin("https://unpkg.com");
-});
+var builder = InfiniFrameApplication.CreateBuilder(args)
+ .WithWindow(window => window
+ .AddTrustedOrigin("https://xyz")
+ .AddTrustedOrigin("https://cdn.jsdelivr.net")
+ .AddTrustedOrigin("https://unpkg.com"));
+builder.UseBlazorWebView(configuration => configuration.RootComponents.Add("app"));
+var app = builder.Build();
```
If you need broad compatibility during migration, you can opt in to trusting all origins:
```csharp
-var app = InfiniFrameBlazorAppBuilder.CreateDefault(windowBuilder: wb => {
- wb.SetTrustAllOrigins(true);
-});
+var builder = InfiniFrameApplication.CreateBuilder(args)
+ .WithWindow(window => window.SetTrustAllOrigins(true));
+builder.UseBlazorWebView(configuration => configuration.RootComponents.Add("app"));
+var app = builder.Build();
```
`SetTrustAllOrigins(true)` is intentionally high-risk and should be treated as a temporary dev-time switch, not a production default.
diff --git a/docs/package-lock.json b/docs/package-lock.json
index 9ff18a11c..961e3a4fa 100644
--- a/docs/package-lock.json
+++ b/docs/package-lock.json
@@ -13,7 +13,7 @@
"@docusaurus/preset-classic": "^3.10.2",
"clsx": "^2.1.1",
"prism-react-renderer": "^2.4.1",
- "serialize-javascript": "^7.1.0"
+ "serialize-javascript": "^7.1.1"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "^3.10.2",
@@ -6605,9 +6605,9 @@
}
},
"node_modules/body-parser": {
- "version": "1.20.6",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz",
- "integrity": "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==",
+ "version": "1.20.8",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.8.tgz",
+ "integrity": "sha512-JNcyFQ64OiijEkPzUBTCe+hyPXUD/3LEldGQ6iF5LR1w00mx9o7xtDWHXBY2iItjdCFGoilOLNQbH943ut7pHA==",
"license": "MIT",
"dependencies": {
"bytes": "~3.1.2",
@@ -6618,7 +6618,7 @@
"http-errors": "~2.0.1",
"iconv-lite": "~0.4.24",
"on-finished": "~2.4.1",
- "qs": "~6.15.1",
+ "qs": "~6.16.0",
"raw-body": "~2.5.3",
"type-is": "~1.6.18",
"unpipe": "~1.0.0"
@@ -7213,9 +7213,9 @@
"license": "MIT"
},
"node_modules/colord": {
- "version": "2.9.3",
- "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz",
- "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==",
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/colord/-/colord-2.10.0.tgz",
+ "integrity": "sha512-AidJptpBJmjTclAp9BkLwJi0T93fo5epJnbaZslpg6QVzpHjAiveF55mE9AcUJiGMqRHgMDY8soMsQtuNYMHfw==",
"license": "MIT"
},
"node_modules/colorette": {
@@ -8799,9 +8799,9 @@
}
},
"node_modules/express": {
- "version": "4.22.2",
- "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz",
- "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==",
+ "version": "4.22.3",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.22.3.tgz",
+ "integrity": "sha512-Bdcs4+3qlpVlx2NRn6fgX2Ue2/gGRaPeawebgclM0ERSCqDpA+owF1fdPwjJUTAJWMTuAaxjDf+hzb0/4eKvvw==",
"license": "MIT",
"dependencies": {
"accepts": "~1.3.8",
@@ -8823,9 +8823,9 @@
"methods": "~1.1.2",
"on-finished": "~2.4.1",
"parseurl": "~1.3.3",
- "path-to-regexp": "~0.1.12",
+ "path-to-regexp": "~0.1.13",
"proxy-addr": "~2.0.7",
- "qs": "~6.15.1",
+ "qs": "~6.16.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
"send": "~0.19.0",
@@ -8933,9 +8933,9 @@
"license": "MIT"
},
"node_modules/fast-uri": {
- "version": "3.1.5",
- "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz",
- "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==",
+ "version": "3.1.8",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz",
+ "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==",
"funding": [
{
"type": "github",
@@ -10537,9 +10537,9 @@
}
},
"node_modules/joi": {
- "version": "17.13.4",
- "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.4.tgz",
- "integrity": "sha512-1RuuER6kmt8K8I3nIWvPZKi5RQCb568ZPyY4Pwjlua+yo+63ZTmIwxLZH0heBmiKN4uxjvCiarDrjaeH84xicQ==",
+ "version": "17.13.8",
+ "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.8.tgz",
+ "integrity": "sha512-iPKOGmiRw1jxf/JOPwxmCcUQAOdF359mdzYiP2DJ+TMX0YK2zjK3D+zYOaGjpumWxOFF/l2xVWjRVK5bGSLdEw==",
"license": "BSD-3-Clause",
"dependencies": {
"@hapi/hoek": "^9.3.0",
@@ -10556,9 +10556,9 @@
"license": "MIT"
},
"node_modules/js-yaml": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz",
- "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==",
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz",
+ "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==",
"funding": [
{
"type": "github",
@@ -15611,9 +15611,9 @@
}
},
"node_modules/qs": {
- "version": "6.15.3",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz",
- "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==",
+ "version": "6.16.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz",
+ "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==",
"license": "BSD-3-Clause",
"dependencies": {
"es-define-property": "^1.0.1",
@@ -16646,9 +16646,9 @@
}
},
"node_modules/serialize-javascript": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.1.0.tgz",
- "integrity": "sha512-RNEqWOyhhUQYN9V1GfHwu9AR/g+NTciH6Z5u3/no6X3/w+04J2lVDL+svFQVXgXrEGBMG2puMVN3gq2SNGuTGw==",
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.1.1.tgz",
+ "integrity": "sha512-k3CMsaIvvdSwm8oLB4MXSl0wH2/cwlH7xGcnRd2DaeRmBkbzYmyT8j0tsX60DwD1eRwHTpNpH8ljKu9oUT1MeQ==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=20.0.0"
@@ -17322,9 +17322,9 @@
"license": "MIT"
},
"node_modules/svgo": {
- "version": "3.3.4",
- "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.4.tgz",
- "integrity": "sha512-GsNRis4e8jxn2Y9ENz/8lbJ93CstG8svtMnuRaHbiF2LTJ5tK0/q3t/URPq9Zc7zVWBJnNnJMIp6bevK7bSmNg==",
+ "version": "3.3.5",
+ "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.5.tgz",
+ "integrity": "sha512-8SQMzdrvWaD8deUmrnYB+ASyxBVgWUOilg+A75nE/76WdLpj6LopCwiAVvkzkcqy/9b7t2Mg7faFLjg0ZRcZ3w==",
"license": "MIT",
"dependencies": {
"commander": "^7.2.0",
diff --git a/docs/package.json b/docs/package.json
index a04c1ccb4..74fc80b4d 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -14,7 +14,7 @@
"@docusaurus/preset-classic": "^3.10.2",
"clsx": "^2.1.1",
"prism-react-renderer": "^2.4.1",
- "serialize-javascript": "^7.1.0"
+ "serialize-javascript": "^7.1.1"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "^3.10.2",
@@ -24,7 +24,7 @@
"overrides": {
"brace-expansion": "^5.0.9",
"webpack": "^5.109.2",
- "serialize-javascript": "^7.1.0",
+ "serialize-javascript": "^7.1.1",
"uuid": "^14.0.1",
"image-size": "npm:image-size-next@^2.1.1"
},
diff --git a/docs/sidebars.ts b/docs/sidebars.ts
index d31305b83..4b38f6d87 100644
--- a/docs/sidebars.ts
+++ b/docs/sidebars.ts
@@ -8,6 +8,7 @@ const sidebars: SidebarsConfig = {
label: "Guides",
items: [
"guides/getting-started",
+ "guides/macos-application-lifecycle",
"guides/pack-tool",
"guides/trim-aot-compatibility",
{
diff --git a/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Components/App.razor b/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Components/App.razor
index dab47188f..e16e6152e 100644
--- a/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Components/App.razor
+++ b/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Components/App.razor
@@ -1,3 +1,4 @@
+@using global::MudBlazor
diff --git a/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Components/Pages/Index.razor b/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Components/Pages/Index.razor
index 053720acd..666add529 100644
--- a/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Components/Pages/Index.razor
+++ b/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Components/Pages/Index.razor
@@ -98,7 +98,7 @@
@if (_eventLog.Count == 0) {
No events yet. Click a button above.
}
- @foreach (var entry in _eventLog) {
+ @foreach (string entry in _eventLog) {
@entry
diff --git a/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Program.cs b/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Program.cs
index 5b4217353..4c0d7abb2 100644
--- a/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Program.cs
+++ b/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Program.cs
@@ -1,14 +1,15 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
+using InfiniFrameExample.BlazorWebView.MudBlazor.Components;
+using Serilog;
+using System.Drawing;
using InfiniFrame;
+using InfiniFrame.Application;
using InfiniFrame.BlazorWebView;
-using InfiniFrameExample.BlazorWebView.MudBlazor.Components;
-using MudBlazor.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-using Serilog;
-using System.Drawing;
+using MudBlazor.Services;
namespace InfiniFrameExample.BlazorWebView.MudBlazor;
// ---------------------------------------------------------------------------------------------------------------------
@@ -19,15 +20,15 @@ public static class Program {
private static void Main(string[] args) {
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
- .WriteTo.Async(static c => c.Console())
+ .WriteTo.Console()
.CreateLogger();
try {
Log.Information("Starting InfiniFrame BlazorWebView MudBlazor example...");
- var appBuilder = InfiniFrameBlazorAppBuilder.CreateDefault(args);
-
- appBuilder.Services
+ InfiniFrameApplicationBuilder builder = InfiniFrameApplication.CreateBuilder(args);
+
+ builder.Services
.AddLogging(config => {
config.ClearProviders();
config.AddSerilog();
@@ -38,17 +39,16 @@ private static void Main(string[] args) {
})
.AddMudServices();
- appBuilder.RootComponents.Add("app");
+ builder.WithWindow(window => window
+ .SetIconFile("wwwroot/favicon.ico")
+ .SetLocation(new Point(100, 100))
+ .SetSize(new Size(800, 600)));
- appBuilder.WithInfiniFrameWindowBuilder(builder => {
- builder
- .SetIconFile("wwwroot/favicon.ico")
- .SetLocation(new Point(100, 100))
- .SetSize(new Size(800, 600));
+ builder.UseBlazorWebView(configuration => {
+ configuration.RootComponents.Add("app");
});
-
- Log.Information("Building InfiniFrame application...");
- InfiniFrameBlazorApp app = appBuilder.Build();
+
+ InfiniFrameApplication app = builder.Build();
Log.Information("Running application...");
app.Run();
diff --git a/examples/InfiniFrameExample.BlazorWebView/Components/Pages/Counter.razor b/examples/InfiniFrameExample.BlazorWebView/Components/Pages/Counter.razor
index 1f397f19e..df78da1db 100644
--- a/examples/InfiniFrameExample.BlazorWebView/Components/Pages/Counter.razor
+++ b/examples/InfiniFrameExample.BlazorWebView/Components/Pages/Counter.razor
@@ -16,6 +16,6 @@
_currentCount++;
Window.Features.Position.CenterOnCurrentMonitor();
- Logger.LogWarning("{a}", Window.Features.State.IsZoomEnabled);
+ Logger.LogWarning("IsZoomEnabled: {State}", Window.Features.State.IsZoomEnabled);
}
}
diff --git a/examples/InfiniFrameExample.BlazorWebView/Program.cs b/examples/InfiniFrameExample.BlazorWebView/Program.cs
index 6fb0fd100..b619ca4a7 100644
--- a/examples/InfiniFrameExample.BlazorWebView/Program.cs
+++ b/examples/InfiniFrameExample.BlazorWebView/Program.cs
@@ -1,13 +1,14 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
+using System.Drawing;
using InfiniFrame;
+using InfiniFrame.Application;
using InfiniFrame.BlazorWebView;
using InfiniFrameExample.BlazorWebView.Components;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Serilog;
-using System.Drawing;
namespace InfiniFrameExample.BlazorWebView;
// ---------------------------------------------------------------------------------------------------------------------
@@ -16,41 +17,32 @@ namespace InfiniFrameExample.BlazorWebView;
public static class Program {
[STAThread]
private static void Main(string[] args) {
- var appBuilder = InfiniFrameBlazorAppBuilder.CreateDefault(args);
+ InfiniFrameApplicationBuilder builder = InfiniFrameApplication.CreateBuilder(args);
- appBuilder.Services.AddLogging(config => {
- config.ClearProviders();
- config.AddSerilog();
+ builder.Services.AddLogging(config => {
+ config.ClearProviders();
+ config.AddSerilog();
});
- appBuilder.Services.AddSerilog(config => {
- config.WriteTo.Async(static c => c.Console())
- .MinimumLevel.Debug();
+ builder.Services.AddSerilog(config => {
+ config.WriteTo.Async(static c => c.Console())
+ .MinimumLevel.Debug();
});
- // register the root component and selector
- appBuilder.RootComponents.Add("app");
-
- appBuilder.WithInfiniFrameWindowBuilder(builder => {
- builder
- // .SetTransparent(true)
- // .SetChromeless(true)
- // .SetResizable(true)
- .SetIconFile("wwwroot/favicon.ico")
- .SetWindowsAppUserModelId("InfiniLore.InfiniFrameExample.BlazorWebView")
- // .Center()
- // .SetUseOsDefaultSize(true)
- // .SetUseOsDefaultLocation(true);
- // .SetTitle("InfiniLore InfiniFrame.Blazor Sample")
- .SetLocation(new Point(100, 100))
- .SetSize(new Size(800, 600))
- // .SetMaxSize(new Size(800, 600))
- // .SetMinSize(new Size(600, 400))
- ;
+ builder.WithWindow(window => window
+ .SetIconFile("wwwroot/favicon.ico")
+ .SetLocation(new Point(100, 100))
+ .SetSize(new Size(800, 600)));
+
+
+ builder.UseBlazorWebView(configuration => {
+ // register the root component and selector
+ configuration.RootComponents.Add("app");
+
});
- InfiniFrameBlazorApp app = appBuilder.Build();
+ InfiniFrameApplication app = builder.Build();
app.Run();
}
-}
\ No newline at end of file
+}
diff --git a/examples/InfiniFrameExample.BlazorWebView/README.md b/examples/InfiniFrameExample.BlazorWebView/README.md
index f6e4249ca..2236ee026 100644
--- a/examples/InfiniFrameExample.BlazorWebView/README.md
+++ b/examples/InfiniFrameExample.BlazorWebView/README.md
@@ -5,7 +5,7 @@ Demonstrates the minimal setup for hosting a Blazor application inside a native
## What it shows
-- `InfiniFrameBlazorAppBuilder.CreateDefault()` entry point
+- `InfiniFrameApplication.CreateBuilder()` entry point
- Registering a root Blazor component (``) mapped to the `#app` selector
- Configuring the window: size, position, and icon file
- Integrating Serilog for structured logging via `Microsoft.Extensions.Logging`
@@ -20,15 +20,17 @@ dotnet run --project examples/InfiniFrameExample.BlazorWebView
## Key code
```csharp
-var appBuilder = InfiniFrameBlazorAppBuilder.CreateDefault(args);
+var appBuilder = InfiniFrameApplication.CreateBuilder(args);
-appBuilder.RootComponents.Add("app");
+appBuilder.UseBlazorWebView(config => {
+ config.RootComponents.Add("app");
-appBuilder.WithInfiniFrameWindowBuilder(builder => builder
+ config.ConfigureWindow(builder => builder
.SetIconFile("favicon.ico")
.SetLocation(new Point(100, 100))
.SetSize(new Size(800, 600))
-);
+ );
+});
appBuilder.Build().Run();
```
diff --git a/examples/InfiniFrameExample.NativeMenu/Program.cs b/examples/InfiniFrameExample.NativeMenu/Program.cs
index e7812b714..3185dfc4b 100644
--- a/examples/InfiniFrameExample.NativeMenu/Program.cs
+++ b/examples/InfiniFrameExample.NativeMenu/Program.cs
@@ -4,6 +4,7 @@
using InfiniFrame;
using System.Drawing;
using System.Text.Json;
+using InfiniFrame.Application;
namespace InfiniFrameExample.NativeMenu;
// ---------------------------------------------------------------------------------------------------------------------
@@ -71,18 +72,19 @@ public static void Main(string[] args) {
]
);
- IInfiniFrameWindow window = InfiniFrameWindowBuilder.Create()
- .SetTitle("InfiniFrame Native Menu Example")
- .SetSize(new Size(960, 640))
- .CenteredOnMainMonitor()
- .SetMenuBar(menuBar)
- .UseEmbeddedWwwrootAssets(
- scheme: "app",
- includePhysicalFallback: true,
- physicalWwwrootPath: Path.Join(AppContext.BaseDirectory, "wwwroot"),
- setStartUrl: true
- )
- .RegisterWebMessageReceivedHandler((win, message) => {
+ InfiniFrameApplication.CreateBuilder(args)
+ .WithWindow(builder => builder
+ .SetTitle("InfiniFrame Native Menu Example")
+ .SetSize(new Size(960, 640))
+ .CenteredOnMainMonitor()
+ .SetMenuBar(menuBar)
+ .UseEmbeddedWwwrootAssets(
+ scheme: "app",
+ includePhysicalFallback: true,
+ physicalWwwrootPath: Path.Join(AppContext.BaseDirectory, "wwwroot"),
+ setStartUrl: true
+ )
+ .RegisterWebMessageReceivedHandler((win, message) => {
string? action = ExtractAction(message);
if (action == null) return;
@@ -109,10 +111,9 @@ public static void Main(string[] args) {
win.SendWebMessage($"status:Action: {action}");
break;
}
- })
- .Build();
-
- window.WaitForClose();
+ }))
+ .Build()
+ .Run();
}
private static string? ExtractAction(string rawMessage) {
diff --git a/examples/InfiniFrameExample.TrimAotSmoke/Program.cs b/examples/InfiniFrameExample.TrimAotSmoke/Program.cs
index 9e413fde6..e3eb57632 100644
--- a/examples/InfiniFrameExample.TrimAotSmoke/Program.cs
+++ b/examples/InfiniFrameExample.TrimAotSmoke/Program.cs
@@ -2,6 +2,7 @@
// Imports
// ---------------------------------------------------------------------------------------------------------------------
using InfiniFrame;
+using InfiniFrame.Application;
namespace InfiniFrameExample.TrimAotSmoke;
@@ -11,13 +12,13 @@ namespace InfiniFrameExample.TrimAotSmoke;
public static class Program {
[STAThread]
public static void Main() {
- IInfiniFrameWindow window = InfiniFrameWindowBuilder.Create()
- .SetTitle("InfiniFrame Trim/AOT Smoke")
- .SetSize(800, 600)
- .CenteredOnMainMonitor()
- .UseEmbeddedWwwrootAssets()
- .Build();
-
- window.WaitForClose();
+ InfiniFrameApplication.CreateBuilder()
+ .WithWindow(builder => builder
+ .SetTitle("InfiniFrame Trim/AOT Smoke")
+ .SetSize(800, 600)
+ .CenteredOnMainMonitor()
+ .UseEmbeddedWwwrootAssets())
+ .Build()
+ .Run();
}
-}
\ No newline at end of file
+}
diff --git a/examples/README.md b/examples/README.md
index 9fe382eb2..fa78dee05 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -24,7 +24,7 @@ All examples require the native `InfiniFrame.Native` library to be built first
```bash
# From the repo root build the native library for your platform
-cmake -S src/InfiniFrame.Native -B artifacts/native/windows/x64/Debug -DCMAKE_BUILD_TYPE=Debug
+cmake -S src/InfiniFrame.NativeBridge/Native -B artifacts/native/windows/x64/Debug -DCMAKE_BUILD_TYPE=Debug
cmake --build artifacts/native/windows/x64/Debug
# Then run an example
diff --git a/examples/SingleFileExe/InfiniFrameExample.SingleFileExe.MudBlazor/Components/App.razor b/examples/SingleFileExe/InfiniFrameExample.SingleFileExe.MudBlazor/Components/App.razor
index bc8e178c9..021aae981 100644
--- a/examples/SingleFileExe/InfiniFrameExample.SingleFileExe.MudBlazor/Components/App.razor
+++ b/examples/SingleFileExe/InfiniFrameExample.SingleFileExe.MudBlazor/Components/App.razor
@@ -1,4 +1,5 @@
-
+@using global::MudBlazor
+
diff --git a/examples/SingleFileExe/InfiniFrameExample.SingleFileExe.MudBlazor/Program.cs b/examples/SingleFileExe/InfiniFrameExample.SingleFileExe.MudBlazor/Program.cs
index 09b29ccad..898f5dae3 100644
--- a/examples/SingleFileExe/InfiniFrameExample.SingleFileExe.MudBlazor/Program.cs
+++ b/examples/SingleFileExe/InfiniFrameExample.SingleFileExe.MudBlazor/Program.cs
@@ -2,8 +2,10 @@
// Imports
// ---------------------------------------------------------------------------------------------------------------------
using InfiniFrame;
+using InfiniFrame.Application;
using InfiniFrame.BlazorWebView;
using InfiniFrame.SingleFile;
+using InfiniFrame.Window.Features.WebMessaging.Handlers;
using InfiniFrameExample.SingleFileExe.MudBlazor.Components;
using MudBlazor.Services;
using Serilog;
@@ -25,9 +27,11 @@ private static void Main(string[] args) {
try {
Log.Information("Starting InfiniFrame MudBlazor example...");
- var appBuilder = InfiniFrameBlazorAppBuilder.CreateDefault(args);
-
- appBuilder.Services
+ InfiniFrameApplicationBuilder builder = InfiniFrameApplication.CreateBuilder(args);
+ builder.WithWindow(window => window
+ .SetIconFile("wwwroot/favicon.ico")
+ .RegisterOpenExternalTargetWebMessageHandler());
+ builder.Services
.AddLogging(config => {
config.ClearProviders();
config.AddSerilog();
@@ -37,17 +41,12 @@ private static void Main(string[] args) {
.MinimumLevel.Debug();
})
.AddMudServices();
-
- appBuilder.RootComponents.Add("app");
-
- appBuilder.WindowBuilder
- .SetIconFile("wwwroot/favicon.ico")
- .RegisterOpenExternalTargetWebMessageHandler();
-
- InfiniFrameSingleFile.AddSingleFileRequirements(appBuilder);
-
- Log.Information("Building InfiniFrame application...");
- InfiniFrameBlazorApp application = appBuilder.Build();
+ InfiniFrameApplication application = builder
+ .UseBlazorWebView(configuration => {
+ configuration.RootComponents.Add("app");
+ configuration.AddSingleFileRequirements();
+ })
+ .Build();
Log.Information("Running application...");
application.Run();
diff --git a/examples/SingleFileExe/InfiniFrameExample.SingleFileExe.React/Program.cs b/examples/SingleFileExe/InfiniFrameExample.SingleFileExe.React/Program.cs
index e2817d90c..c37e87912 100644
--- a/examples/SingleFileExe/InfiniFrameExample.SingleFileExe.React/Program.cs
+++ b/examples/SingleFileExe/InfiniFrameExample.SingleFileExe.React/Program.cs
@@ -1,5 +1,6 @@
-using InfiniFrame;
using System.Drawing;
+using InfiniFrame;
+using InfiniFrame.Application;
using InfiniFrame.SingleFile;
namespace InfiniFrameExample.SingleFileExe.React;
@@ -9,14 +10,15 @@ public static class Program {
public static void Main(string[] args) {
InfiniFrameSingleFile.Initialize();
- IInfiniFrameWindowBuilder builder = InfiniFrameWindowBuilder.Create()
- .SetTitle("InfiniFrame + React")
- .SetSize(new Size(960, 640))
- .CenteredOnMainMonitor();
-
- builder.AddSingleFileRequirements();
-
- IInfiniFrameWindow window = builder.Build();
- window.WaitForClose();
+ InfiniFrameApplication.CreateBuilder(args)
+ .WithWindow(builder => {
+ builder
+ .SetTitle("InfiniFrame + React")
+ .SetSize(new Size(960, 640))
+ .CenteredOnMainMonitor();
+ builder.AddSingleFileRequirements();
+ })
+ .Build()
+ .Run();
}
}
diff --git a/examples/SingleFileExe/InfiniFrameExample.SingleFileExe.Vue/Program.cs b/examples/SingleFileExe/InfiniFrameExample.SingleFileExe.Vue/Program.cs
index 866c60e20..729e22acc 100644
--- a/examples/SingleFileExe/InfiniFrameExample.SingleFileExe.Vue/Program.cs
+++ b/examples/SingleFileExe/InfiniFrameExample.SingleFileExe.Vue/Program.cs
@@ -1,8 +1,9 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-using InfiniFrame;
using System.Drawing;
+using InfiniFrame;
+using InfiniFrame.Application;
using InfiniFrame.SingleFile;
namespace InfiniFrameExample.SingleFileExe.Vue;
@@ -14,14 +15,15 @@ public static class Program {
public static void Main(string[] args) {
InfiniFrameSingleFile.Initialize();
- IInfiniFrameWindowBuilder builder = InfiniFrameWindowBuilder.Create()
- .SetTitle("InfiniFrame + Vue")
- .SetSize(new Size(960, 640))
- .CenteredOnMainMonitor();
-
- builder.AddSingleFileRequirements();
-
- IInfiniFrameWindow window = builder.Build();
- window.WaitForClose();
+ InfiniFrameApplication.CreateBuilder(args)
+ .WithWindow(builder => {
+ builder
+ .SetTitle("InfiniFrame + Vue")
+ .SetSize(new Size(960, 640))
+ .CenteredOnMainMonitor();
+ builder.AddSingleFileRequirements();
+ })
+ .Build()
+ .Run();
}
}
diff --git a/examples/SingleFileExe/InfiniFrameExample.SingleFileExe/Program.cs b/examples/SingleFileExe/InfiniFrameExample.SingleFileExe/Program.cs
index 10387ee05..61a9fab20 100644
--- a/examples/SingleFileExe/InfiniFrameExample.SingleFileExe/Program.cs
+++ b/examples/SingleFileExe/InfiniFrameExample.SingleFileExe/Program.cs
@@ -1,8 +1,9 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-using InfiniFrame;
using System.Drawing;
+using InfiniFrame;
+using InfiniFrame.Application;
using InfiniFrame.SingleFile;
namespace InfiniFrameExample.SingleFileExe;
@@ -14,15 +15,15 @@ public static class Program {
public static void Main(string[] args) {
InfiniFrameSingleFile.Initialize();
- IInfiniFrameWindowBuilder builder = InfiniFrameWindowBuilder.Create()
- .SetTitle("InfiniFrame Embedded wwwroot")
- .SetSize(new Size(960, 640))
- .CenteredOnMainMonitor();
-
- builder.AddSingleFileRequirements();
-
- IInfiniFrameWindow window = builder.Build();
-
- window.WaitForClose();
+ InfiniFrameApplication.CreateBuilder(args)
+ .WithWindow(builder => {
+ builder
+ .SetTitle("InfiniFrame Embedded wwwroot")
+ .SetSize(new Size(960, 640))
+ .CenteredOnMainMonitor();
+ builder.AddSingleFileRequirements();
+ })
+ .Build()
+ .Run();
}
}
diff --git a/examples/WebApp/InfiniFrameExample.WebApp.Blazor/Components/Pages/Counter.razor b/examples/WebApp/InfiniFrameExample.WebApp.Blazor/Components/Pages/Counter.razor
index caf260369..b7a836d71 100644
--- a/examples/WebApp/InfiniFrameExample.WebApp.Blazor/Components/Pages/Counter.razor
+++ b/examples/WebApp/InfiniFrameExample.WebApp.Blazor/Components/Pages/Counter.razor
@@ -17,6 +17,6 @@
_currentCount++;
Window.Features.Position.CenterOnCurrentMonitor();
- Logger.LogWarning("{a}", Window.Features.State.IsZoomEnabled);
+ Logger.LogWarning("{A}", Window.Features.State.IsZoomEnabled);
}
-}
\ No newline at end of file
+}
diff --git a/examples/WebApp/InfiniFrameExample.WebApp.Blazor/Program.cs b/examples/WebApp/InfiniFrameExample.WebApp.Blazor/Program.cs
index 65969dbf3..f660c0de8 100644
--- a/examples/WebApp/InfiniFrameExample.WebApp.Blazor/Program.cs
+++ b/examples/WebApp/InfiniFrameExample.WebApp.Blazor/Program.cs
@@ -1,11 +1,13 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
+using System.Drawing;
using InfiniFrame;
+using InfiniFrame.Application;
using InfiniFrame.WebServer;
+using InfiniFrame.Window.Features.WebMessaging.Handlers;
using InfiniFrameExample.WebApp.Blazor.Components;
using Serilog;
-using System.Drawing;
namespace InfiniFrameExample.WebApp.Blazor;
// ---------------------------------------------------------------------------------------------------------------------
@@ -17,9 +19,16 @@ private static void Main(string[] args) {
// -------------------------------------------------------------------------------------------------------------
// Builder
// -------------------------------------------------------------------------------------------------------------
- InfiniFrameWebApplicationBuilder appBuilder = InfiniFrameWebApplication.CreateBuilder(args);
+ InfiniFrameApplicationBuilder builder = InfiniFrameApplication.CreateBuilder(args);
- appBuilder.Services
+ builder.WithWindow(window => window
+ .SetIconFile("wwwroot/favicon.ico")
+ .SetLocation(new Point(100, 100))
+ .SetSize(new Size(800, 600))
+ .RegisterOpenExternalTargetWebMessageHandler()
+ );
+
+ builder.Services
.AddLogging(config => {
config.ClearProviders();
config.AddSerilog();
@@ -31,7 +40,7 @@ private static void Main(string[] args) {
.AddRazorComponents()
.AddInteractiveServerComponents();
- appBuilder.Services.AddHttpClient("ServerApi", (sp, client) => {
+ builder.Services.AddHttpClient("ServerApi", configureClient: (sp, client) => {
var config = sp.GetRequiredService();
// Prefer ASPNETCORE_URLS, then "urls", then a fallback
@@ -45,44 +54,24 @@ private static void Main(string[] args) {
client.BaseAddress = new Uri(baseUrl);
});
- appBuilder.Services.AddScoped(sp => sp.GetRequiredService().CreateClient("ServerApi"));
-
- appBuilder.Services.AddInfiniFrameJs();
-
- appBuilder.WebApp.WebHost.UseStaticWebAssets();
+ builder.Services.AddScoped(sp => sp.GetRequiredService().CreateClient("ServerApi"));
- appBuilder.WindowBuilder
- // .SetTransparent(true)
- // .SetChromeless(true)
- // .SetResizable(true)
- .SetIconFile("wwwroot/favicon.ico")
- // .Center()
- // .SetUseOsDefaultSize(true)
- // .SetUseOsDefaultLocation(true);
- // .SetTitle("InfiniLore InfiniFrame.Blazor Sample")
- .SetLocation(new Point(100, 100))
- .SetSize(new Size(800, 600))
- .RegisterOpenExternalTargetWebMessageHandler()
- // .SetMaxSize(new Size(800, 600))
- // .SetMinSize(new Size(600, 400))
- ;
+ builder.Services.AddInfiniFrameJs();
- // -------------------------------------------------------------------------------------------------------------
- // App
- // -------------------------------------------------------------------------------------------------------------
- InfiniFrameWebApplication application = appBuilder.Build();
- application.UseAutoServerClose();
-
- WebApplication webApp = application.WebApp;
+ builder.UseWebServer(web => {
- webApp.UseRouting();
+ web.WebHost.UseStaticWebAssets();
- webApp.UseAntiforgery();
- webApp.MapStaticAssets();
-
- webApp.MapRazorComponents()
- .AddInteractiveServerRenderMode();
+ web.ConfigureWebApplication(webApp => {
+ webApp.UseRouting();
+ webApp.UseAntiforgery();
+ webApp.MapStaticAssets();
+ webApp.MapRazorComponents()
+ .AddInteractiveServerRenderMode();
+ });
+ });
+ InfiniFrameApplication application = builder.Build();
application.Run();
}
-}
\ No newline at end of file
+}
diff --git a/examples/WebApp/InfiniFrameExample.WebApp.Blazor/README.md b/examples/WebApp/InfiniFrameExample.WebApp.Blazor/README.md
index 848223dc4..c9793bdb0 100644
--- a/examples/WebApp/InfiniFrameExample.WebApp.Blazor/README.md
+++ b/examples/WebApp/InfiniFrameExample.WebApp.Blazor/README.md
@@ -4,12 +4,12 @@ Demonstrates hosting a full ASP.NET Core Blazor Server application inside a nati
## What it shows
-- `InfiniFrameWebApplication.CreateBuilder()` entry point
+- `InfiniFrameApplication.CreateBuilder()` entry point
- Blazor Server with `AddRazorComponents()` + `AddInteractiveServerComponents()`
- `HttpClient` factory configured to point at the local Kestrel server
- `AddInfiniFrameJs()` service registration for Blazor component interop
- `RegisterOpenExternalTargetWebMessageHandler()` links with `target="_blank"` open in the default browser
-- `UseAutoServerClose()` server stops when the window is closed
+- Application-owned lifecycle stops the server when the window is closed
- Serilog with async console sink
## Run
@@ -21,19 +21,19 @@ dotnet run --project examples/InfiniFrameExample.WebApp.Blazor
## Key code
```csharp
-InfiniFrameWebApplicationBuilder builder = InfiniFrameWebApplication.CreateBuilder(args);
+InfiniFrameApplicationBuilder builder = InfiniFrameApplication.CreateBuilder(args);
-builder.WebApp.Services.AddRazorComponents()
+builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
-builder.WebApp.Services.AddInfiniFrameJs();
+builder.Services.AddInfiniFrameJs();
-builder.Window
+builder.WithWindow(window => window
.SetSize(new Size(800, 600))
- .RegisterOpenExternalTargetWebMessageHandler();
+ .RegisterOpenExternalTargetWebMessageHandler());
-InfiniFrameWebApplication app = builder.Build();
-app.UseAutoServerClose();
-app.WebApp.MapRazorComponents().AddInteractiveServerRenderMode();
+builder.UseWebServer(web => web.ConfigureWebApplication(application =>
+ application.MapRazorComponents().AddInteractiveServerRenderMode()));
+InfiniFrameApplication app = builder.Build();
app.Run();
```
diff --git a/examples/WebApp/InfiniFrameExample.WebApp.React/Program.cs b/examples/WebApp/InfiniFrameExample.WebApp.React/Program.cs
index b32b42dcc..3e88cbcd3 100644
--- a/examples/WebApp/InfiniFrameExample.WebApp.React/Program.cs
+++ b/examples/WebApp/InfiniFrameExample.WebApp.React/Program.cs
@@ -2,8 +2,9 @@
// Imports
// ---------------------------------------------------------------------------------------------------------------------
using InfiniFrame;
-using InfiniFrame.WebServer;
using System.Drawing;
+using InfiniFrame.Application;
+using InfiniFrame.WebServer;
namespace InfiniFrameExample.WebApp.React;
// ---------------------------------------------------------------------------------------------------------------------
@@ -17,11 +18,10 @@ private sealed class WebMessageCounter {
[STAThread]
public static void Main(string[] args) {
- InfiniFrameWebApplicationBuilder appBuilder = InfiniFrameWebApplication.CreateBuilder(args);
- // WebApplicationBuilder appBuilder = builder.WebApp;
- appBuilder.WebApp.Services.AddSingleton();
-
- appBuilder.WindowBuilder
+ InfiniFrameApplicationBuilder rootBuilder = InfiniFrameApplication.CreateBuilder(args);
+ rootBuilder.Services.AddSingleton();
+ InfiniFrameApplication application = rootBuilder
+ .WithWindow(window => window
.UseOsDefaultSize(false)
.SetResizable()
.CenteredOnMainMonitor()
@@ -39,19 +39,18 @@ public static void Main(string[] args) {
])
, "text/javascript")
)
- .RegisterWebMessageReceivedHandler((IInfiniFrameWindow window, string message, WebMessageCounter counter) => {
+ .RegisterWebMessageReceivedHandler((IInfiniFrameWindow infiniFrameWindow, string message, WebMessageCounter counter) => {
int count = counter.Increment();
string response = $"[{count}] Received message: \"{message}\"";
- window.SendWebMessage(response);
- });
-
- InfiniFrameWebApplication application = appBuilder.Build();
-
- application.UseAutoServerClose();
-
- application.WebApp.UseStaticFiles();
- application.WebApp.MapStaticAssets();
+ infiniFrameWindow.SendWebMessage(response);
+ }))
+ .UseWebServer(builder => {
+ builder.ConfigureWebApplication(webApp => {
+ webApp.MapStaticAssets();
+ });
+ })
+ .Build();
application.Run();
}
-}
\ No newline at end of file
+}
diff --git a/examples/WebApp/InfiniFrameExample.WebApp.React/README.md b/examples/WebApp/InfiniFrameExample.WebApp.React/README.md
index 590377a09..9b770d96e 100644
--- a/examples/WebApp/InfiniFrameExample.WebApp.React/README.md
+++ b/examples/WebApp/InfiniFrameExample.WebApp.React/README.md
@@ -7,7 +7,7 @@ Demonstrates a React frontend served by ASP.NET Core inside an InfiniFrame windo
- `RegisterCustomSchemeHandler("app", ...)` intercepts `app://` requests and returns dynamically generated JavaScript
- `RegisterWebMessageReceivedHandler(...)` receives messages from JavaScript, increments a counter, and echoes a response back via `SendWebMessage`
- A singleton `WebMessageCounter` service accessed inside the message handler via DI
-- `UseAutoServerClose()` server stops when the window is closed
+- Application-owned lifecycle stops the server when the window is closed
## Run
diff --git a/examples/WebApp/InfiniFrameExample.WebApp.React/Source/InfiniFrame.React/package-lock.json b/examples/WebApp/InfiniFrameExample.WebApp.React/Source/InfiniFrame.React/package-lock.json
index 67642730d..40f98d5f0 100644
--- a/examples/WebApp/InfiniFrameExample.WebApp.React/Source/InfiniFrame.React/package-lock.json
+++ b/examples/WebApp/InfiniFrameExample.WebApp.React/Source/InfiniFrame.React/package-lock.json
@@ -8,21 +8,21 @@
"name": "infiniframe.example.react",
"version": "0.0.0",
"dependencies": {
- "react": "^19.2.8",
- "react-dom": "^19.2.8"
+ "react": "^19.3.0",
+ "react-dom": "^19.3.0"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
- "@types/node": "^26.2.0",
- "@types/react": "^19.2.18",
- "@types/react-dom": "^19.2.5",
- "@vitejs/plugin-react": "^6.1.0",
- "eslint": "^10.9.0",
+ "@types/node": "^26.6.2",
+ "@types/react": "^19.3.0",
+ "@types/react-dom": "^19.3.0",
+ "@vitejs/plugin-react": "^6.1.1",
+ "eslint": "^10.11.0",
"eslint-plugin-react-hooks": "^7.1.1",
- "eslint-plugin-react-refresh": "^0.5.4",
- "globals": "^17.11.0",
+ "eslint-plugin-react-refresh": "^0.5.7",
+ "globals": "^17.12.0",
"typescript": "^7.0.2",
- "vite": "^8.2.2"
+ "vite": "^8.3.0"
}
},
"node_modules/@babel/code-frame": {
@@ -265,6 +265,30 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@cacheable/memory": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.2.0.tgz",
+ "integrity": "sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@cacheable/utils": "^2.5.0",
+ "@keyv/bigmap": "^1.3.1",
+ "hookified": "^1.15.1",
+ "keyv": "^5.6.0"
+ }
+ },
+ "node_modules/@cacheable/utils": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.5.0.tgz",
+ "integrity": "sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hashery": "^1.5.1",
+ "keyv": "^5.6.0"
+ }
+ },
"node_modules/@eslint-community/eslint-utils": {
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
@@ -380,9 +404,9 @@
}
},
"node_modules/@eslint/plugin-kit": {
- "version": "0.7.2",
- "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz",
- "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==",
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.3.tgz",
+ "integrity": "sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -509,20 +533,61 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@keyv/bigmap": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz",
+ "integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hashery": "^1.4.0",
+ "hookified": "^1.15.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "peerDependencies": {
+ "keyv": "^5.6.0"
+ }
+ },
+ "node_modules/@keyv/serialize": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz",
+ "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@oxc-project/types": {
- "version": "0.144.0",
- "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.144.0.tgz",
- "integrity": "sha512-nuhZIOLuI6TFQ32I/WnUx+SCPY7SdSKwgnFHydAuoS1+Z4BRcaP+RRJmGzl9lw+0OFF7UmaESf7KQRXaNLHypg==",
+ "version": "0.150.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.150.0.tgz",
+ "integrity": "sha512-rDS5/31E9HfPl/CIzGrn0DOlvBbXFseQ5URJ9sYMfstbKLD/c6Gm9vmRzRGDdAXyOIL4zmO37lc9RIwYqVruZw==",
"dev": true,
"license": "MIT",
"funding": {
- "url": "https://github.com/sponsors/Boshen"
+ "url": "https://github.com/sponsors/oxc-project"
+ }
+ },
+ "node_modules/@rolldown/binding-android-arm-eabi": {
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.9.tgz",
+ "integrity": "sha512-tNISae1QEf/vkb3xkRcjV5SEdzPE97We5IVaa2Z8jSszQPZ8U60B/YCYpw4QI7VidYsBtKavczXf+DyDs9WGxw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-android-arm64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.4.tgz",
- "integrity": "sha512-jHC2cnyKz5xU2fhECtFl8OZ83cYNt13GZQD+0uMJ/X3o+ijmd56okHhTUwxVSHPx1IRVIJEZ1/1pPzeLCU6XKA==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.9.tgz",
+ "integrity": "sha512-YC8YsI30o606GTZi0VyzYlsDKFP8W61i/QzayHDkLbNEz/IShqAmTa+hsJRj13xTHA0H+6fk4b2UmGn+Q/cMlg==",
"cpu": [
"arm64"
],
@@ -537,9 +602,9 @@
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.4.tgz",
- "integrity": "sha512-Dc5mPD8F5F/FS8i01syd7FTF6yB2fVthH/TRkjwJkzUK6EpoxHtqvZQP5Zwq80/5z19TWYHIg1KOHboCgVx/aQ==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.9.tgz",
+ "integrity": "sha512-IwhlH3qK5urrY8hZiEgGkHKEFN901p/p2bjxCxJlr4GyNnF7wYpUvK+Y43uaRYuC4hpfjzbR3SJC3arX1jGvmw==",
"cpu": [
"arm64"
],
@@ -554,9 +619,9 @@
}
},
"node_modules/@rolldown/binding-darwin-x64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.4.tgz",
- "integrity": "sha512-fpDm4oBo6SqLvWUYCmFhdde3U9KH2fRNNMeAnAPAIwxRL345xutL0EtEUcuoxsoazdJGv/MuDBQHlCDrtbvqOg==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.9.tgz",
+ "integrity": "sha512-XxpJfVzFh+jilRxIXUqcfYAYcunIc/XEzIizsOL1fcJee5Sf7H3mH8WlLmfHfluz5amqR88QQo9izKtmMlavAw==",
"cpu": [
"x64"
],
@@ -571,9 +636,9 @@
}
},
"node_modules/@rolldown/binding-freebsd-x64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.4.tgz",
- "integrity": "sha512-rSJoreDE/HoIzoaib6MTp5jQtCTdMHKIvItAKT/ImS6Y6Ww76oUaeMyp4Vc/fAgd/ehji068IxetHXAnqUwN9A==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.9.tgz",
+ "integrity": "sha512-kSfvhmgeWyfkbT3p/1s5vSgboogoah2zkm9fX2zjg2hHxSV7T4KhMWRUUaRk4OXNqoD3QAUeRqLcs1aZOK4U1g==",
"cpu": [
"x64"
],
@@ -588,9 +653,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.4.tgz",
- "integrity": "sha512-/jm8OGHgn7oGaJu3i/qZI9spUGcJ+y/lk43ttQ/iO1tOd9NissG6o97bighBCiL+BKRngmcDuR6ikfwYdJmVuQ==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.9.tgz",
+ "integrity": "sha512-1RVzG17pxqbTfYLC352JlLt6kKLG+6Hr30n8DlIJqsnV5luUDd2Qdx9Ayw1Cabfyb1K9k0jXEZ7evxkRoT+uiw==",
"cpu": [
"arm"
],
@@ -605,9 +670,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.4.tgz",
- "integrity": "sha512-tIP06BeD9EqvECBrPZ+sqdPlYrT+aYaAiu1wYziVx5elRK/ftm33JxVDy2bXGbr6J0CrtirCkR87/X5a2euEng==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.9.tgz",
+ "integrity": "sha512-BXqPvZ2drqVD+/Z8UpKwcs4Mp7grM+eGFku4CAEKrEtcbAsUpzREphK1sogCRZGreVPiMkiiBtw0n3TPteuqvw==",
"cpu": [
"arm64"
],
@@ -625,9 +690,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.4.tgz",
- "integrity": "sha512-Ql1Q0EQqVThvn9VAVlwNzsUvbSFtCMGjLpRRi4pk5i7NZZ4n5ISiLMjHYtus4VQ2PvkSw24zyaCVsiS+sXPj1w==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.9.tgz",
+ "integrity": "sha512-11vWvo8YDwLzukt27J3aYDWU+gg2P7J+ZOmiJ0hkF5BXZDW7pVya7r40MXDy6ya0i9KamoENSVKIugvJNgFXIA==",
"cpu": [
"arm64"
],
@@ -645,9 +710,9 @@
}
},
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.4.tgz",
- "integrity": "sha512-GjbjXD4XXfN19D0LZNbmiCBUoDiRACsYHr0yaIbbn8aFsXjHZifcYqu/W5Er5X2X990WjHXFrxarn5chzItorQ==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.9.tgz",
+ "integrity": "sha512-a1tijMkdwsIARtc0F39ApURROkf3NwqinI6TOiSSWCTR7dT96dffNvMUtDHnq64wKNTIZOIlzKrFvvFUznJiyw==",
"cpu": [
"ppc64"
],
@@ -665,9 +730,9 @@
}
},
"node_modules/@rolldown/binding-linux-s390x-gnu": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.4.tgz",
- "integrity": "sha512-p5WR0NOwaRmJ/B1b6IjEFLLivwEsf3PrdBIhRbhTCQisbo2SvHHpG4ELB/+FgQNnB88LTOF86upmJmbvZdQ2lw==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.9.tgz",
+ "integrity": "sha512-x6SQNdAvv4c3hWqTMaWuawzMX9myaCs/yEmlGsxJzkdClnHW7FbrjQuSiRDhuSYzEYoEMhsaJy9qHG/XNemJPQ==",
"cpu": [
"s390x"
],
@@ -685,9 +750,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.4.tgz",
- "integrity": "sha512-4/GyVjmhR+Tc6HLJvwc1sOhPqAZtySiSMesOZyX6JQ5XBxoTDEMKQzvo07NIK6nTon/SivlZqvhzvuVBNQhObQ==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.9.tgz",
+ "integrity": "sha512-9s0AZ8BFK5/n7B/TBoa2yJE3gI3KURrbXcPBlsAsvjU4VeJKgE90y1YtNxyEUIcHPQkg6/yfF3qihUrcM/Kf0Q==",
"cpu": [
"x64"
],
@@ -705,9 +770,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-musl": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.4.tgz",
- "integrity": "sha512-l9eeLsCNvPpmSXUej0etw/J1eqV0Jj1D5G/xG6YTijmE6dkv6E2QezgWbTfQk63v952DPqrjOCoiqxq7Bw0YUQ==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.9.tgz",
+ "integrity": "sha512-P7VWAmV+WdJluH7ovnRGoiv2i8To7GAZ+kGzfGup635cyL7SyYl3lSUaA3Gp5THf0n/Co5EyEqb2zbqq+nMOHQ==",
"cpu": [
"x64"
],
@@ -725,9 +790,9 @@
}
},
"node_modules/@rolldown/binding-openharmony-arm64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.4.tgz",
- "integrity": "sha512-e0F355MSTMm3+UOqtV3L24gFUp2N5m1f8L/7d56deik6va+AXdrt9F8LbzGpeWGWRbZEDq4m8NVnJDeBtf9DZg==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.9.tgz",
+ "integrity": "sha512-1qixtsE4BK8h+yS3BfmZ09UhA7O/N4IACva6YBr7EBvCJraByTuRcgOTaiA62Tm0vey3UcKXLOaoGHtYmNGEVg==",
"cpu": [
"arm64"
],
@@ -742,9 +807,9 @@
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.4.tgz",
- "integrity": "sha512-AWLi0uBRYh6QlE7OKhiz+phZC0qwtij2QZmhmOdsLdFn64m7oMpooE9ICE3lhm9xMb4SpDo2WbHcxX1iFLFtqw==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.9.tgz",
+ "integrity": "sha512-ok8IQjcEPs1AKZfuEUznVBrJw+gK4soq+bx8b1X2XoMqVClarc1q5JDmVtWXY1xfr6ZuHTAsPXHTgTrqKTZeww==",
"cpu": [
"arm64"
],
@@ -759,9 +824,9 @@
}
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.4.tgz",
- "integrity": "sha512-UwSDJOg3dqCAejWdxclJjCsh3Qq4vLYMDxmyHqo1btz3stK2VqgwNd3mm5tuIwzSlGIQ/1H9Hr+Zn09mrezNqQ==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.9.tgz",
+ "integrity": "sha512-Ip2mXoU0hM0boq3Rf+ekuT653OROSo6aSYcPT1VHE4q52KvyxgFkQgrgb/IEsxOuvQ2fZZbs8khJAyCEPM24/g==",
"cpu": [
"x64"
],
@@ -804,19 +869,19 @@
"license": "MIT"
},
"node_modules/@types/node": {
- "version": "26.2.0",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz",
- "integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==",
+ "version": "26.6.2",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.2.tgz",
+ "integrity": "sha512-X1P21scMv4zGKLYqjdGjaKa7COa0RKVYYZZN/NfvLQ1JegxFhdhpZG/Lyn8AXx6CDUavKAd11v6BvfpkDByK8g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "undici-types": "~8.3.0"
+ "undici-types": "~8.9.0"
}
},
"node_modules/@types/react": {
- "version": "19.2.18",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz",
- "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==",
+ "version": "19.3.0",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz",
+ "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -824,13 +889,13 @@
}
},
"node_modules/@types/react-dom": {
- "version": "19.2.5",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.5.tgz",
- "integrity": "sha512-fMPwH9v7r/pp43yUd2/Mbiex5KouJwwR3dzHkhLREUC6764VyDsqxhAxv6OFEYR1RhjOyD1naqba8ECDBe7ZQg==",
+ "version": "19.3.0",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.3.0.tgz",
+ "integrity": "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==",
"dev": true,
"license": "MIT",
"peerDependencies": {
- "@types/react": "^19.2.0"
+ "@types/react": "^19.3.0"
}
},
"node_modules/@typescript/typescript-aix-ppc64": {
@@ -1174,9 +1239,9 @@
}
},
"node_modules/@vitejs/plugin-react": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.0.tgz",
- "integrity": "sha512-qd2BzUBehkov86WFhg0JkEFEYyCLG9uPCe6qWTY/kRlss9OvJrOF2UbIWT7p+8IzZHkEu0DNGHc4HSv+JdDLsw==",
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.1.tgz",
+ "integrity": "sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1254,9 +1319,9 @@
}
},
"node_modules/baseline-browser-mapping": {
- "version": "2.10.38",
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.38.tgz",
- "integrity": "sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==",
+ "version": "2.11.25",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.25.tgz",
+ "integrity": "sha512-gMmEShwwq7FJqMwvfRwvCl00v4kN+KOfJqXn+f4nrufak5gNHJOksd/60Dvjuz7sI8Y5WiSFBa8FEYr+zoyqCw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -1280,9 +1345,9 @@
}
},
"node_modules/browserslist": {
- "version": "4.28.2",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
- "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
+ "version": "4.29.0",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.0.tgz",
+ "integrity": "sha512-3GSvyjvDI4Dur1Meg2BekJquu5uF+9R9a1+5M1Mde192eZoXbeXjzgOsgqPS2V8D5wrrip0gR5Hf/GhWQ9ZzaA==",
"dev": true,
"funding": [
{
@@ -1300,11 +1365,11 @@
],
"license": "MIT",
"dependencies": {
- "baseline-browser-mapping": "^2.10.12",
- "caniuse-lite": "^1.0.30001782",
- "electron-to-chromium": "^1.5.328",
- "node-releases": "^2.0.36",
- "update-browserslist-db": "^1.2.3"
+ "baseline-browser-mapping": "^2.11.23",
+ "caniuse-lite": "^1.0.30001810",
+ "electron-to-chromium": "^1.5.427",
+ "node-releases": "^2.0.55",
+ "update-browserslist-db": "^1.3.3"
},
"bin": {
"browserslist": "cli.js"
@@ -1313,10 +1378,24 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
+ "node_modules/cacheable": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.5.0.tgz",
+ "integrity": "sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@cacheable/memory": "^2.2.0",
+ "@cacheable/utils": "^2.5.0",
+ "hookified": "^1.15.0",
+ "keyv": "^5.6.0",
+ "qified": "^0.10.1"
+ }
+ },
"node_modules/caniuse-lite": {
- "version": "1.0.30001799",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz",
- "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==",
+ "version": "1.0.30001810",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz",
+ "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==",
"dev": true,
"funding": [
{
@@ -1399,9 +1478,9 @@
}
},
"node_modules/electron-to-chromium": {
- "version": "1.5.376",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.376.tgz",
- "integrity": "sha512-cUVA7/RvbFTEuw/i3obUwDTRIXojaxkResf+ibByPFxjc6XK3VNtcQXV0NSbAlJ0FMjcJGgftVVB4Qo184EXvA==",
+ "version": "1.5.433",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.433.tgz",
+ "integrity": "sha512-5lCAbyZBjtmUt/RAGHRqrL2q0oEFRThDAsZHHDn9XHa89Qw7gMYOeSicBTy+AHfvo0r6vwsZvqNJTQIQy1BLzA==",
"dev": true,
"license": "ISC"
},
@@ -1429,9 +1508,9 @@
}
},
"node_modules/eslint": {
- "version": "10.9.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.9.0.tgz",
- "integrity": "sha512-5KeEOJZBfEVA47boFiBsf+6MmmJpffM7qEBg4pLla2e4nlKgdKlqCW0oSLOGsT8Wl5uCGJptLV1bkaiShj90Gw==",
+ "version": "10.11.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.11.0.tgz",
+ "integrity": "sha512-P7a6UEEqb9G95MYAtqkmsTbVXIYyzIfl6NGOIJk162PaahFxFyeGcrlXYFSiagECg4sEm8IseJdZBKR3rx6MsQ==",
"dev": true,
"license": "MIT",
"workspaces": [
@@ -1443,7 +1522,7 @@
"@eslint/config-array": "^0.23.5",
"@eslint/config-helpers": "^0.7.0",
"@eslint/core": "^1.2.1",
- "@eslint/plugin-kit": "^0.7.2",
+ "@eslint/plugin-kit": "^0.7.3",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.2",
@@ -1458,7 +1537,7 @@
"esquery": "^1.7.0",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
- "file-entry-cache": "^8.0.0",
+ "file-entry-cache": "11.1.5 || >11.1.6 <12",
"find-up": "^5.0.0",
"glob-parent": "^6.0.2",
"ignore": "^5.2.0",
@@ -1508,9 +1587,9 @@
}
},
"node_modules/eslint-plugin-react-refresh": {
- "version": "0.5.4",
- "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.4.tgz",
- "integrity": "sha512-7bqTKz7T0r+HKWFarNXByDE9/5+73wI2ru+M3zuqGbR7s/b/5/pQJXZoufWlrngqGqoZto73ZkGumCdLxk+4rw==",
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.7.tgz",
+ "integrity": "sha512-XhJSzLljuYD4UjNuFGJu2v7aD3sqNVK12w5/DCUfrNfHWegZo2+TsavNx8MuxMwEquuFAzNbNfXKU0WoZ+YUIg==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@@ -1653,16 +1732,13 @@
}
},
"node_modules/file-entry-cache": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
- "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "version": "11.1.5",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.5.tgz",
+ "integrity": "sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "flat-cache": "^4.0.0"
- },
- "engines": {
- "node": ">=16.0.0"
+ "flat-cache": "^6.1.23"
}
},
"node_modules/find-up": {
@@ -1683,23 +1759,21 @@
}
},
"node_modules/flat-cache": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
- "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "version": "6.1.23",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.23.tgz",
+ "integrity": "sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "flatted": "^3.2.9",
- "keyv": "^4.5.4"
- },
- "engines": {
- "node": ">=16"
+ "cacheable": "^2.5.0",
+ "flatted": "^3.4.2",
+ "hookified": "^1.15.0"
}
},
"node_modules/flatted": {
- "version": "3.4.2",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
- "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
+ "version": "3.4.4",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz",
+ "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==",
"dev": true,
"license": "ISC"
},
@@ -1742,9 +1816,9 @@
}
},
"node_modules/globals": {
- "version": "17.11.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-17.11.0.tgz",
- "integrity": "sha512-Z2I8hM+PbJDXQDq3Icgpzv+mPdwr68iZUU9d5WW4FuXfDUQfkZaZuvjMv42/5crNyw154+9+VWXbYrUgDXbxNw==",
+ "version": "17.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-17.12.0.tgz",
+ "integrity": "sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1754,6 +1828,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/hashery": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.1.tgz",
+ "integrity": "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hookified": "^1.15.0"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
"node_modules/hermes-estree": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
@@ -1771,6 +1858,13 @@
"hermes-estree": "0.25.1"
}
},
+ "node_modules/hookified": {
+ "version": "1.15.1",
+ "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz",
+ "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -1841,13 +1935,6 @@
"node": ">=6"
}
},
- "node_modules/json-buffer": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
- "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@@ -1876,13 +1963,13 @@
}
},
"node_modules/keyv": {
- "version": "4.5.4",
- "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
- "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "version": "5.6.0",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz",
+ "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "json-buffer": "3.0.1"
+ "@keyv/serialize": "^1.1.1"
}
},
"node_modules/levn": {
@@ -2222,9 +2309,9 @@
"license": "MIT"
},
"node_modules/nanoid": {
- "version": "3.3.18",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
- "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
+ "version": "3.3.19",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz",
+ "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==",
"dev": true,
"funding": [
{
@@ -2248,9 +2335,9 @@
"license": "MIT"
},
"node_modules/node-releases": {
- "version": "2.0.48",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.48.tgz",
- "integrity": "sha512-1uz8041X6LoI6ZSdZacM9lVY28vuzDlSKitnpbSNK0RfKoIJkX29NBPVEFXhnuSuEOA9Ww0xnPJ+ILWbGAv8DA==",
+ "version": "2.0.56",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.56.tgz",
+ "integrity": "sha512-x0InOIyzgdk+eyaWaRJFH5snEtiImgBgblZ2CyPrLmqqcuMQkEvcDPHbzqbD8eDsSeJbVOjn+crzyzHaM4D+/A==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2335,9 +2422,9 @@
"license": "ISC"
},
"node_modules/picomatch": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
- "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2348,9 +2435,9 @@
}
},
"node_modules/postcss": {
- "version": "8.5.26",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz",
- "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==",
+ "version": "8.5.28",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz",
+ "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==",
"dev": true,
"funding": [
{
@@ -2368,7 +2455,7 @@
],
"license": "MIT",
"dependencies": {
- "nanoid": "^3.3.17",
+ "nanoid": "^3.3.18",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
@@ -2396,35 +2483,55 @@
"node": ">=6"
}
},
+ "node_modules/qified": {
+ "version": "0.10.1",
+ "resolved": "https://registry.npmjs.org/qified/-/qified-0.10.1.tgz",
+ "integrity": "sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hookified": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/qified/node_modules/hookified": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/hookified/-/hookified-2.2.0.tgz",
+ "integrity": "sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/react": {
- "version": "19.2.8",
- "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz",
- "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==",
+ "version": "19.3.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.3.0.tgz",
+ "integrity": "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
- "version": "19.2.8",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz",
- "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==",
+ "version": "19.3.0",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.0.tgz",
+ "integrity": "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==",
"license": "MIT",
"dependencies": {
- "scheduler": "^0.27.0"
+ "scheduler": "^0.28.0"
},
"peerDependencies": {
- "react": "^19.2.8"
+ "react": "^19.3.0"
}
},
"node_modules/rolldown": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.4.tgz",
- "integrity": "sha512-rSr7irW0K7QRWzjdJXqZowkcRdDtjRduh43rBltnVKd0VFq839l1lJoDvGJb6gl7+4rTTCrPWu+YfujUL8Ug7w==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.9.tgz",
+ "integrity": "sha512-hx/Pv0N1haXRb11qkfnK5MXB/iqr7i0yjWQqmO9uHqZpBgQSqzc8UsSnEpalsh+j1I8qQ2CkXAkJC8Br3dKSlg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@oxc-project/types": "=0.144.0",
+ "@oxc-project/types": "=0.150.0",
"@rolldown/pluginutils": "^1.0.0"
},
"bin": {
@@ -2434,26 +2541,27 @@
"node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
- "@rolldown/binding-android-arm64": "1.2.4",
- "@rolldown/binding-darwin-arm64": "1.2.4",
- "@rolldown/binding-darwin-x64": "1.2.4",
- "@rolldown/binding-freebsd-x64": "1.2.4",
- "@rolldown/binding-linux-arm-gnueabihf": "1.2.4",
- "@rolldown/binding-linux-arm64-gnu": "1.2.4",
- "@rolldown/binding-linux-arm64-musl": "1.2.4",
- "@rolldown/binding-linux-ppc64-gnu": "1.2.4",
- "@rolldown/binding-linux-s390x-gnu": "1.2.4",
- "@rolldown/binding-linux-x64-gnu": "1.2.4",
- "@rolldown/binding-linux-x64-musl": "1.2.4",
- "@rolldown/binding-openharmony-arm64": "1.2.4",
- "@rolldown/binding-win32-arm64-msvc": "1.2.4",
- "@rolldown/binding-win32-x64-msvc": "1.2.4"
+ "@rolldown/binding-android-arm-eabi": "1.2.9",
+ "@rolldown/binding-android-arm64": "1.2.9",
+ "@rolldown/binding-darwin-arm64": "1.2.9",
+ "@rolldown/binding-darwin-x64": "1.2.9",
+ "@rolldown/binding-freebsd-x64": "1.2.9",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.2.9",
+ "@rolldown/binding-linux-arm64-gnu": "1.2.9",
+ "@rolldown/binding-linux-arm64-musl": "1.2.9",
+ "@rolldown/binding-linux-ppc64-gnu": "1.2.9",
+ "@rolldown/binding-linux-s390x-gnu": "1.2.9",
+ "@rolldown/binding-linux-x64-gnu": "1.2.9",
+ "@rolldown/binding-linux-x64-musl": "1.2.9",
+ "@rolldown/binding-openharmony-arm64": "1.2.9",
+ "@rolldown/binding-win32-arm64-msvc": "1.2.9",
+ "@rolldown/binding-win32-x64-msvc": "1.2.9"
}
},
"node_modules/scheduler": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
- "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz",
+ "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==",
"license": "MIT"
},
"node_modules/semver": {
@@ -2565,16 +2673,16 @@
}
},
"node_modules/undici-types": {
- "version": "8.3.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz",
- "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==",
+ "version": "8.9.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz",
+ "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==",
"dev": true,
"license": "MIT"
},
"node_modules/update-browserslist-db": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
- "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz",
+ "integrity": "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==",
"dev": true,
"funding": [
{
@@ -2613,16 +2721,16 @@
}
},
"node_modules/vite": {
- "version": "8.2.2",
- "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz",
- "integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==",
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.0.tgz",
+ "integrity": "sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"lightningcss": "^1.33.0",
- "picomatch": "^4.0.5",
- "postcss": "^8.5.26",
- "rolldown": "~1.2.4",
+ "picomatch": "^4.0.7",
+ "postcss": "^8.5.28",
+ "rolldown": "~1.2.6",
"tinyglobby": "^0.2.17"
},
"bin": {
@@ -2639,7 +2747,7 @@
},
"peerDependencies": {
"@types/node": "^20.19.0 || >=22.12.0",
- "@vitejs/devtools": "^0.4.0 || ^0.5.0",
+ "@vitejs/devtools": "^0.7.1",
"esbuild": "^0.27.0 || ^0.28.0",
"jiti": ">=1.21.0",
"less": "^4.0.0",
diff --git a/examples/WebApp/InfiniFrameExample.WebApp.React/Source/InfiniFrame.React/package.json b/examples/WebApp/InfiniFrameExample.WebApp.React/Source/InfiniFrame.React/package.json
index c7ab37994..d4c4b7158 100644
--- a/examples/WebApp/InfiniFrameExample.WebApp.React/Source/InfiniFrame.React/package.json
+++ b/examples/WebApp/InfiniFrameExample.WebApp.React/Source/InfiniFrame.React/package.json
@@ -10,20 +10,20 @@
"react.preview": "vite preview"
},
"dependencies": {
- "react": "^19.2.8",
- "react-dom": "^19.2.8"
+ "react": "^19.3.0",
+ "react-dom": "^19.3.0"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
- "@types/node": "^26.2.0",
- "@types/react": "^19.2.18",
- "@types/react-dom": "^19.2.5",
- "@vitejs/plugin-react": "^6.1.0",
- "eslint": "^10.9.0",
+ "@types/node": "^26.6.2",
+ "@types/react": "^19.3.0",
+ "@types/react-dom": "^19.3.0",
+ "@vitejs/plugin-react": "^6.1.1",
+ "eslint": "^10.11.0",
"eslint-plugin-react-hooks": "^7.1.1",
- "eslint-plugin-react-refresh": "^0.5.4",
- "globals": "^17.11.0",
+ "eslint-plugin-react-refresh": "^0.5.7",
+ "globals": "^17.12.0",
"typescript": "^7.0.2",
- "vite": "^8.2.2"
+ "vite": "^8.3.0"
}
}
diff --git a/examples/WebApp/InfiniFrameExample.WebApp.Vue/Program.cs b/examples/WebApp/InfiniFrameExample.WebApp.Vue/Program.cs
index 50a6541e9..93e073071 100644
--- a/examples/WebApp/InfiniFrameExample.WebApp.Vue/Program.cs
+++ b/examples/WebApp/InfiniFrameExample.WebApp.Vue/Program.cs
@@ -1,9 +1,11 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
+using System.Drawing;
using InfiniFrame;
+using InfiniFrame.Application;
using InfiniFrame.WebServer;
-using System.Drawing;
+using InfiniFrame.Window.Features.WebMessaging.Handlers;
namespace InfiniFrameExample.WebApp.Vue;
// ---------------------------------------------------------------------------------------------------------------------
@@ -12,37 +14,35 @@ namespace InfiniFrameExample.WebApp.Vue;
public static class Program {
[STAThread]
public static void Main(string[] args) {
- InfiniFrameWebApplicationBuilder appBuilder = InfiniFrameWebApplication.CreateBuilder(args);
- // WebApplicationBuilder appBuilder = builder.WebApp;
-
- if (OperatingSystem.IsWindows() || OperatingSystem.IsLinux()) appBuilder.WindowBuilder.Debugging.SetRemoteDebuggingPort(9222);
-
- appBuilder.WindowBuilder
- .CenteredOnMainMonitor()
- // .SetTransparent(true)
- // .SetUseOsDefaultSize(false)
- .SetTitle("InfiniLore InfiniFrame.NET VUE Sample")
- .SetSize(new Size(800, 600))
- .SetLocation(1000, 0)
- .RegisterFullScreenWebMessageHandler()
- .RegisterOpenExternalTargetWebMessageHandler()
- .RegisterTitleChangedWebMessageHandler()
- .RegisterWindowManagementWebMessageHandler()
- .RegisterWebMessageReceivedHandler((_, message) => {
- // ReSharper disable twice UnusedVariable
- string response = $"Received message: \"{message}\"";
-
- // ... do something with the message
+ InfiniFrameApplication application = InfiniFrameApplication.CreateBuilder(args)
+ .WithWindow(window => {
+ if (OperatingSystem.IsWindows() || OperatingSystem.IsLinux()) window.Debugging.SetRemoteDebuggingPort(9222);
+ window
+ .CenteredOnMainMonitor()
+ // .SetTransparent(true)
+ // .SetUseOsDefaultSize(false)
+ .SetTitle("InfiniLore InfiniFrame.NET VUE Sample")
+ .SetSize(new Size(800, 600))
+ .SetLocation(1000, 0)
+ .RegisterFullScreenWebMessageHandler()
+ .RegisterOpenExternalTargetWebMessageHandler()
+ .RegisterTitleChangedWebMessageHandler()
+ .RegisterWindowManagementWebMessageHandler()
+ .RegisterWebMessageReceivedHandler((_, message) => {
+ // ReSharper disable twice UnusedVariable
+ string response = $"Received message: \"{message}\"";
+
+ // ... do something with the message
+ })
+ ;
})
- ;
-
- InfiniFrameWebApplication application = appBuilder.Build();
-
- application.UseAutoServerClose();
-
- application.WebApp.UseStaticFiles();
- application.WebApp.MapStaticAssets();
+ .UseWebServer(builder => {
+ builder.ConfigureWebApplication(webApp => {
+ webApp.MapStaticAssets();
+ });
+ })
+ .Build();
application.Run();
}
-}
\ No newline at end of file
+}
diff --git a/examples/WebApp/InfiniFrameExample.WebApp.Vue/Source/InfiniFrame.Vue/package-lock.json b/examples/WebApp/InfiniFrameExample.WebApp.Vue/Source/InfiniFrame.Vue/package-lock.json
index 4a8befc43..d8954c27a 100644
--- a/examples/WebApp/InfiniFrameExample.WebApp.Vue/Source/InfiniFrame.Vue/package-lock.json
+++ b/examples/WebApp/InfiniFrameExample.WebApp.Vue/Source/InfiniFrame.Vue/package-lock.json
@@ -8,14 +8,14 @@
"name": "infiniframe.example.vue",
"version": "0.0.0",
"dependencies": {
- "vue": "^3.5.40"
+ "vue": "^3.5.43"
},
"devDependencies": {
- "@types/node": "^26.2.0",
- "@vitejs/plugin-vue": "^6.0.8",
+ "@types/node": "^26.6.2",
+ "@vitejs/plugin-vue": "^6.0.9",
"@vue/tsconfig": "^0.9.1",
"typescript": "^6.0.3",
- "vite": "^8.2.2",
+ "vite": "^8.3.0",
"vue-tsc": "^3.3.11"
}
},
@@ -38,12 +38,12 @@
}
},
"node_modules/@babel/parser": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
- "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
+ "version": "7.29.9",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz",
+ "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==",
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.29.7"
+ "@babel/types": "^7.29.8"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -53,9 +53,9 @@
}
},
"node_modules/@babel/types": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
- "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz",
+ "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==",
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.29.7",
@@ -66,25 +66,42 @@
}
},
"node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
- "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz",
+ "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==",
"license": "MIT"
},
"node_modules/@oxc-project/types": {
- "version": "0.144.0",
- "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.144.0.tgz",
- "integrity": "sha512-nuhZIOLuI6TFQ32I/WnUx+SCPY7SdSKwgnFHydAuoS1+Z4BRcaP+RRJmGzl9lw+0OFF7UmaESf7KQRXaNLHypg==",
+ "version": "0.150.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.150.0.tgz",
+ "integrity": "sha512-rDS5/31E9HfPl/CIzGrn0DOlvBbXFseQ5URJ9sYMfstbKLD/c6Gm9vmRzRGDdAXyOIL4zmO37lc9RIwYqVruZw==",
"dev": true,
"license": "MIT",
"funding": {
- "url": "https://github.com/sponsors/Boshen"
+ "url": "https://github.com/sponsors/oxc-project"
+ }
+ },
+ "node_modules/@rolldown/binding-android-arm-eabi": {
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.9.tgz",
+ "integrity": "sha512-tNISae1QEf/vkb3xkRcjV5SEdzPE97We5IVaa2Z8jSszQPZ8U60B/YCYpw4QI7VidYsBtKavczXf+DyDs9WGxw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-android-arm64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.4.tgz",
- "integrity": "sha512-jHC2cnyKz5xU2fhECtFl8OZ83cYNt13GZQD+0uMJ/X3o+ijmd56okHhTUwxVSHPx1IRVIJEZ1/1pPzeLCU6XKA==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.9.tgz",
+ "integrity": "sha512-YC8YsI30o606GTZi0VyzYlsDKFP8W61i/QzayHDkLbNEz/IShqAmTa+hsJRj13xTHA0H+6fk4b2UmGn+Q/cMlg==",
"cpu": [
"arm64"
],
@@ -99,9 +116,9 @@
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.4.tgz",
- "integrity": "sha512-Dc5mPD8F5F/FS8i01syd7FTF6yB2fVthH/TRkjwJkzUK6EpoxHtqvZQP5Zwq80/5z19TWYHIg1KOHboCgVx/aQ==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.9.tgz",
+ "integrity": "sha512-IwhlH3qK5urrY8hZiEgGkHKEFN901p/p2bjxCxJlr4GyNnF7wYpUvK+Y43uaRYuC4hpfjzbR3SJC3arX1jGvmw==",
"cpu": [
"arm64"
],
@@ -116,9 +133,9 @@
}
},
"node_modules/@rolldown/binding-darwin-x64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.4.tgz",
- "integrity": "sha512-fpDm4oBo6SqLvWUYCmFhdde3U9KH2fRNNMeAnAPAIwxRL345xutL0EtEUcuoxsoazdJGv/MuDBQHlCDrtbvqOg==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.9.tgz",
+ "integrity": "sha512-XxpJfVzFh+jilRxIXUqcfYAYcunIc/XEzIizsOL1fcJee5Sf7H3mH8WlLmfHfluz5amqR88QQo9izKtmMlavAw==",
"cpu": [
"x64"
],
@@ -133,9 +150,9 @@
}
},
"node_modules/@rolldown/binding-freebsd-x64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.4.tgz",
- "integrity": "sha512-rSJoreDE/HoIzoaib6MTp5jQtCTdMHKIvItAKT/ImS6Y6Ww76oUaeMyp4Vc/fAgd/ehji068IxetHXAnqUwN9A==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.9.tgz",
+ "integrity": "sha512-kSfvhmgeWyfkbT3p/1s5vSgboogoah2zkm9fX2zjg2hHxSV7T4KhMWRUUaRk4OXNqoD3QAUeRqLcs1aZOK4U1g==",
"cpu": [
"x64"
],
@@ -150,9 +167,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.4.tgz",
- "integrity": "sha512-/jm8OGHgn7oGaJu3i/qZI9spUGcJ+y/lk43ttQ/iO1tOd9NissG6o97bighBCiL+BKRngmcDuR6ikfwYdJmVuQ==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.9.tgz",
+ "integrity": "sha512-1RVzG17pxqbTfYLC352JlLt6kKLG+6Hr30n8DlIJqsnV5luUDd2Qdx9Ayw1Cabfyb1K9k0jXEZ7evxkRoT+uiw==",
"cpu": [
"arm"
],
@@ -167,9 +184,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.4.tgz",
- "integrity": "sha512-tIP06BeD9EqvECBrPZ+sqdPlYrT+aYaAiu1wYziVx5elRK/ftm33JxVDy2bXGbr6J0CrtirCkR87/X5a2euEng==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.9.tgz",
+ "integrity": "sha512-BXqPvZ2drqVD+/Z8UpKwcs4Mp7grM+eGFku4CAEKrEtcbAsUpzREphK1sogCRZGreVPiMkiiBtw0n3TPteuqvw==",
"cpu": [
"arm64"
],
@@ -187,9 +204,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.4.tgz",
- "integrity": "sha512-Ql1Q0EQqVThvn9VAVlwNzsUvbSFtCMGjLpRRi4pk5i7NZZ4n5ISiLMjHYtus4VQ2PvkSw24zyaCVsiS+sXPj1w==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.9.tgz",
+ "integrity": "sha512-11vWvo8YDwLzukt27J3aYDWU+gg2P7J+ZOmiJ0hkF5BXZDW7pVya7r40MXDy6ya0i9KamoENSVKIugvJNgFXIA==",
"cpu": [
"arm64"
],
@@ -207,9 +224,9 @@
}
},
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.4.tgz",
- "integrity": "sha512-GjbjXD4XXfN19D0LZNbmiCBUoDiRACsYHr0yaIbbn8aFsXjHZifcYqu/W5Er5X2X990WjHXFrxarn5chzItorQ==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.9.tgz",
+ "integrity": "sha512-a1tijMkdwsIARtc0F39ApURROkf3NwqinI6TOiSSWCTR7dT96dffNvMUtDHnq64wKNTIZOIlzKrFvvFUznJiyw==",
"cpu": [
"ppc64"
],
@@ -227,9 +244,9 @@
}
},
"node_modules/@rolldown/binding-linux-s390x-gnu": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.4.tgz",
- "integrity": "sha512-p5WR0NOwaRmJ/B1b6IjEFLLivwEsf3PrdBIhRbhTCQisbo2SvHHpG4ELB/+FgQNnB88LTOF86upmJmbvZdQ2lw==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.9.tgz",
+ "integrity": "sha512-x6SQNdAvv4c3hWqTMaWuawzMX9myaCs/yEmlGsxJzkdClnHW7FbrjQuSiRDhuSYzEYoEMhsaJy9qHG/XNemJPQ==",
"cpu": [
"s390x"
],
@@ -247,9 +264,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.4.tgz",
- "integrity": "sha512-4/GyVjmhR+Tc6HLJvwc1sOhPqAZtySiSMesOZyX6JQ5XBxoTDEMKQzvo07NIK6nTon/SivlZqvhzvuVBNQhObQ==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.9.tgz",
+ "integrity": "sha512-9s0AZ8BFK5/n7B/TBoa2yJE3gI3KURrbXcPBlsAsvjU4VeJKgE90y1YtNxyEUIcHPQkg6/yfF3qihUrcM/Kf0Q==",
"cpu": [
"x64"
],
@@ -267,9 +284,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-musl": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.4.tgz",
- "integrity": "sha512-l9eeLsCNvPpmSXUej0etw/J1eqV0Jj1D5G/xG6YTijmE6dkv6E2QezgWbTfQk63v952DPqrjOCoiqxq7Bw0YUQ==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.9.tgz",
+ "integrity": "sha512-P7VWAmV+WdJluH7ovnRGoiv2i8To7GAZ+kGzfGup635cyL7SyYl3lSUaA3Gp5THf0n/Co5EyEqb2zbqq+nMOHQ==",
"cpu": [
"x64"
],
@@ -287,9 +304,9 @@
}
},
"node_modules/@rolldown/binding-openharmony-arm64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.4.tgz",
- "integrity": "sha512-e0F355MSTMm3+UOqtV3L24gFUp2N5m1f8L/7d56deik6va+AXdrt9F8LbzGpeWGWRbZEDq4m8NVnJDeBtf9DZg==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.9.tgz",
+ "integrity": "sha512-1qixtsE4BK8h+yS3BfmZ09UhA7O/N4IACva6YBr7EBvCJraByTuRcgOTaiA62Tm0vey3UcKXLOaoGHtYmNGEVg==",
"cpu": [
"arm64"
],
@@ -304,9 +321,9 @@
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.4.tgz",
- "integrity": "sha512-AWLi0uBRYh6QlE7OKhiz+phZC0qwtij2QZmhmOdsLdFn64m7oMpooE9ICE3lhm9xMb4SpDo2WbHcxX1iFLFtqw==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.9.tgz",
+ "integrity": "sha512-ok8IQjcEPs1AKZfuEUznVBrJw+gK4soq+bx8b1X2XoMqVClarc1q5JDmVtWXY1xfr6ZuHTAsPXHTgTrqKTZeww==",
"cpu": [
"arm64"
],
@@ -321,9 +338,9 @@
}
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.4.tgz",
- "integrity": "sha512-UwSDJOg3dqCAejWdxclJjCsh3Qq4vLYMDxmyHqo1btz3stK2VqgwNd3mm5tuIwzSlGIQ/1H9Hr+Zn09mrezNqQ==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.9.tgz",
+ "integrity": "sha512-Ip2mXoU0hM0boq3Rf+ekuT653OROSo6aSYcPT1VHE4q52KvyxgFkQgrgb/IEsxOuvQ2fZZbs8khJAyCEPM24/g==",
"cpu": [
"x64"
],
@@ -345,19 +362,19 @@
"license": "MIT"
},
"node_modules/@types/node": {
- "version": "26.2.0",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz",
- "integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==",
+ "version": "26.6.2",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.2.tgz",
+ "integrity": "sha512-X1P21scMv4zGKLYqjdGjaKa7COa0RKVYYZZN/NfvLQ1JegxFhdhpZG/Lyn8AXx6CDUavKAd11v6BvfpkDByK8g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "undici-types": "~8.3.0"
+ "undici-types": "~8.9.0"
}
},
"node_modules/@vitejs/plugin-vue": {
- "version": "6.0.8",
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.8.tgz",
- "integrity": "sha512-0ZjgOg7oO6farnNGup7yvoM/YXZV84OZxHAwtflItNa/6zzQyVb5LNxyea3FEKEX2XlagIKzrlH7wwxkKgtiew==",
+ "version": "6.0.9",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.9.tgz",
+ "integrity": "sha512-rD/MORlhaZMlXWW0rEn4FB4wMinWC0z/D7Ye160S5+Rs1mC8ZDAdcDp4SjKfUhG5t8q1ktcPVw4xuTkPRlzrSA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -401,53 +418,53 @@
}
},
"node_modules/@vue/compiler-core": {
- "version": "3.5.40",
- "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.40.tgz",
- "integrity": "sha512-39E8IgOhTbVDnoJFMKc2DvYnypcZwUqgUhQkccva/0m6FUwtIKSGV7n1hpVmYcFaoRAwf9pBcwnKlCEsN63ZEQ==",
+ "version": "3.5.43",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.43.tgz",
+ "integrity": "sha512-zdiLhnbe1QQqgDT8xZMpNmyqZ3qlI+/Q/FHQco57Kwl/b05HhCzN6eVGN9QU9rbga4CrS0H5SYY8VGHZCt/1Hg==",
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.29.7",
- "@vue/shared": "3.5.40",
+ "@babel/parser": "^7.29.8",
+ "@vue/shared": "3.5.43",
"entities": "^7.0.1",
"estree-walker": "^2.0.2",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-dom": {
- "version": "3.5.40",
- "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.40.tgz",
- "integrity": "sha512-pwkx4vqlqOspFstrcmzwkKLePVMD3PT65imRzLhanU2V1Fj4K13g6OXjanOyzw3aTAuRk84BOmY8f3rEHqPaVA==",
+ "version": "3.5.43",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.43.tgz",
+ "integrity": "sha512-PEZoAk3NQmsn/ejMzSOCyTYqwGqczrWm70PuhBKjjv1+TCoQAaO/zOqNwjV+honlNstT5ILxtc+8r8UUfj+iEQ==",
"license": "MIT",
"dependencies": {
- "@vue/compiler-core": "3.5.40",
- "@vue/shared": "3.5.40"
+ "@vue/compiler-core": "3.5.43",
+ "@vue/shared": "3.5.43"
}
},
"node_modules/@vue/compiler-sfc": {
- "version": "3.5.40",
- "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.40.tgz",
- "integrity": "sha512-gIf497P4kpuALcvs5n3AEg1Vdn0pSY4XbjASIfHNYF1/MP3T2Mf2STERTubysBxCRxzJGJYtF/O7vwJrxFB3Vw==",
+ "version": "3.5.43",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.43.tgz",
+ "integrity": "sha512-FCbrG3XNCRl+js3huuKx4IVHBLTvMkJhVepjbxSPu1gn4yWLaYtGNQdjJGZaMytXB6qb76qQDDmDSLy/vkmleQ==",
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.29.7",
- "@vue/compiler-core": "3.5.40",
- "@vue/compiler-dom": "3.5.40",
- "@vue/compiler-ssr": "3.5.40",
- "@vue/shared": "3.5.40",
+ "@babel/parser": "^7.29.8",
+ "@vue/compiler-core": "3.5.43",
+ "@vue/compiler-dom": "3.5.43",
+ "@vue/compiler-ssr": "3.5.43",
+ "@vue/shared": "3.5.43",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.21",
- "postcss": "^8.5.19",
+ "postcss": "^8.5.28",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-ssr": {
- "version": "3.5.40",
- "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.40.tgz",
- "integrity": "sha512-rrE5xiXG663+vHCHa3J9p2z5OcBRjXmoqenprJxAFQxg5pSshzeBiCE6pu46axapRJ2Adk0YDA2BRZVjiHXnhg==",
+ "version": "3.5.43",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.43.tgz",
+ "integrity": "sha512-GF62orf7KiJX9RqrHNGrYBudsQGD0OhJ5nDs90O8UiDuS40+YMYomiXu6w6EuvtXuRDc3MSNis3EaaSKAVSWpg==",
"license": "MIT",
"dependencies": {
- "@vue/compiler-dom": "3.5.40",
- "@vue/shared": "3.5.40"
+ "@vue/compiler-dom": "3.5.43",
+ "@vue/shared": "3.5.43"
}
},
"node_modules/@vue/language-core": {
@@ -467,51 +484,51 @@
}
},
"node_modules/@vue/reactivity": {
- "version": "3.5.40",
- "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.40.tgz",
- "integrity": "sha512-B7ot9UlUZOi1zbq61/LvE88ZLTV8IlajTdiZTAEiDQgrnIMIZoPr9kGw0Zw46ObW62O9+H/Be3kMbfb7kYPQZA==",
+ "version": "3.5.43",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.43.tgz",
+ "integrity": "sha512-G/c9GyOZNI2jVaaS6OX1EF1SSFSv7H0ERqNTl4+DTFMlZmB5eVAB53aLQNam/7NL2NPtaDD7RdVrzf8uJzMuOA==",
"license": "MIT",
"dependencies": {
- "@vue/shared": "3.5.40"
+ "@vue/shared": "3.5.43"
}
},
"node_modules/@vue/runtime-core": {
- "version": "3.5.40",
- "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.40.tgz",
- "integrity": "sha512-KAZLweuZ6uUJPK1PMSQPgBU5gCjgrrfjUhSglmU9NhH+Zjepa8cnwSydPWDWHDwOgY4g3VcZ+PljbiHlURNCbw==",
+ "version": "3.5.43",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.43.tgz",
+ "integrity": "sha512-hU6U6VnVhBGQDpvlnnDlIB8ZGJBiOcgk2lh/0InltHiz3D8oSkluvuvY+do1G2H3+udeKFsmaBlgVYP7gXQzEw==",
"license": "MIT",
"dependencies": {
- "@vue/reactivity": "3.5.40",
- "@vue/shared": "3.5.40"
+ "@vue/reactivity": "3.5.43",
+ "@vue/shared": "3.5.43"
}
},
"node_modules/@vue/runtime-dom": {
- "version": "3.5.40",
- "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.40.tgz",
- "integrity": "sha512-ZfrX8ssZQds900L9pr8AuK05ddnMsR4MPMZr8cPN9GoqoPWcXLhjvvbIA2SMv+7a97sJ1vv9pj/zxK0Cq/eEFQ==",
+ "version": "3.5.43",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.43.tgz",
+ "integrity": "sha512-Bb2Jc0YjjJdMt1SJmb9b2L/IWd3I8lIT9x9eS/xvvP9CiVgna0ffua74xKRmt4/uSJ+0r4iN8ex1jrqkhQGWQw==",
"license": "MIT",
"dependencies": {
- "@vue/reactivity": "3.5.40",
- "@vue/runtime-core": "3.5.40",
- "@vue/shared": "3.5.40",
+ "@vue/reactivity": "3.5.43",
+ "@vue/runtime-core": "3.5.43",
+ "@vue/shared": "3.5.43",
"csstype": "^3.2.3"
}
},
"node_modules/@vue/server-renderer": {
- "version": "3.5.40",
- "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.40.tgz",
- "integrity": "sha512-XNJym9WpevhTVt1HuwOrCRJ5Q+9z4BjTMrDtjTrvx74SmUll8spNTw6whWJa9mEkO4PKn5TihI/bm/8ds2QVJw==",
+ "version": "3.5.43",
+ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.43.tgz",
+ "integrity": "sha512-l2Ygjv9NehV94PSBxNWsAHC0j/eIIKbn92mBuWXAPGLnn6HfJ6MH5ubsd+Nk0YoZ5FRuxWI1P2VSoh+dbPQhCQ==",
"license": "MIT",
"dependencies": {
- "@vue/compiler-ssr": "3.5.40",
- "@vue/runtime-dom": "3.5.40",
- "@vue/shared": "3.5.40"
+ "@vue/compiler-ssr": "3.5.43",
+ "@vue/runtime-dom": "3.5.43",
+ "@vue/shared": "3.5.43"
}
},
"node_modules/@vue/shared": {
- "version": "3.5.40",
- "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.40.tgz",
- "integrity": "sha512-WxnBtruIqOoV3rA4jeKDWzrYI5h7Cp4+pjwDi8kWGHz+IslhiN+wguLVVhtv2l8VoU02rzDCVfDjgCl1lNpZVg==",
+ "version": "3.5.43",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.43.tgz",
+ "integrity": "sha512-uksS7YGMR5NZyr4JNq0Rp+QyLns0ueaz20KwzIPW9R0LH1Vnt4E+XUM29PNseEbf1www2gOuhuDi5AKOIXag9Q==",
"license": "MIT"
},
"node_modules/@vue/tsconfig": {
@@ -897,9 +914,9 @@
"license": "MIT"
},
"node_modules/nanoid": {
- "version": "3.3.18",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
- "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
+ "version": "3.3.19",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz",
+ "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==",
"funding": [
{
"type": "github",
@@ -928,9 +945,9 @@
"license": "ISC"
},
"node_modules/picomatch": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
- "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -941,9 +958,9 @@
}
},
"node_modules/postcss": {
- "version": "8.5.26",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz",
- "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==",
+ "version": "8.5.28",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz",
+ "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==",
"funding": [
{
"type": "opencollective",
@@ -960,7 +977,7 @@
],
"license": "MIT",
"dependencies": {
- "nanoid": "^3.3.17",
+ "nanoid": "^3.3.18",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
@@ -969,13 +986,13 @@
}
},
"node_modules/rolldown": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.4.tgz",
- "integrity": "sha512-rSr7irW0K7QRWzjdJXqZowkcRdDtjRduh43rBltnVKd0VFq839l1lJoDvGJb6gl7+4rTTCrPWu+YfujUL8Ug7w==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.9.tgz",
+ "integrity": "sha512-hx/Pv0N1haXRb11qkfnK5MXB/iqr7i0yjWQqmO9uHqZpBgQSqzc8UsSnEpalsh+j1I8qQ2CkXAkJC8Br3dKSlg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@oxc-project/types": "=0.144.0",
+ "@oxc-project/types": "=0.150.0",
"@rolldown/pluginutils": "^1.0.0"
},
"bin": {
@@ -985,20 +1002,21 @@
"node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
- "@rolldown/binding-android-arm64": "1.2.4",
- "@rolldown/binding-darwin-arm64": "1.2.4",
- "@rolldown/binding-darwin-x64": "1.2.4",
- "@rolldown/binding-freebsd-x64": "1.2.4",
- "@rolldown/binding-linux-arm-gnueabihf": "1.2.4",
- "@rolldown/binding-linux-arm64-gnu": "1.2.4",
- "@rolldown/binding-linux-arm64-musl": "1.2.4",
- "@rolldown/binding-linux-ppc64-gnu": "1.2.4",
- "@rolldown/binding-linux-s390x-gnu": "1.2.4",
- "@rolldown/binding-linux-x64-gnu": "1.2.4",
- "@rolldown/binding-linux-x64-musl": "1.2.4",
- "@rolldown/binding-openharmony-arm64": "1.2.4",
- "@rolldown/binding-win32-arm64-msvc": "1.2.4",
- "@rolldown/binding-win32-x64-msvc": "1.2.4"
+ "@rolldown/binding-android-arm-eabi": "1.2.9",
+ "@rolldown/binding-android-arm64": "1.2.9",
+ "@rolldown/binding-darwin-arm64": "1.2.9",
+ "@rolldown/binding-darwin-x64": "1.2.9",
+ "@rolldown/binding-freebsd-x64": "1.2.9",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.2.9",
+ "@rolldown/binding-linux-arm64-gnu": "1.2.9",
+ "@rolldown/binding-linux-arm64-musl": "1.2.9",
+ "@rolldown/binding-linux-ppc64-gnu": "1.2.9",
+ "@rolldown/binding-linux-s390x-gnu": "1.2.9",
+ "@rolldown/binding-linux-x64-gnu": "1.2.9",
+ "@rolldown/binding-linux-x64-musl": "1.2.9",
+ "@rolldown/binding-openharmony-arm64": "1.2.9",
+ "@rolldown/binding-win32-arm64-msvc": "1.2.9",
+ "@rolldown/binding-win32-x64-msvc": "1.2.9"
}
},
"node_modules/source-map-js": {
@@ -1042,23 +1060,23 @@
}
},
"node_modules/undici-types": {
- "version": "8.3.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz",
- "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==",
+ "version": "8.9.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz",
+ "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==",
"dev": true,
"license": "MIT"
},
"node_modules/vite": {
- "version": "8.2.2",
- "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz",
- "integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==",
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.0.tgz",
+ "integrity": "sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"lightningcss": "^1.33.0",
- "picomatch": "^4.0.5",
- "postcss": "^8.5.26",
- "rolldown": "~1.2.4",
+ "picomatch": "^4.0.7",
+ "postcss": "^8.5.28",
+ "rolldown": "~1.2.6",
"tinyglobby": "^0.2.17"
},
"bin": {
@@ -1075,7 +1093,7 @@
},
"peerDependencies": {
"@types/node": "^20.19.0 || >=22.12.0",
- "@vitejs/devtools": "^0.4.0 || ^0.5.0",
+ "@vitejs/devtools": "^0.7.1",
"esbuild": "^0.27.0 || ^0.28.0",
"jiti": ">=1.21.0",
"less": "^4.0.0",
@@ -1134,16 +1152,16 @@
"license": "MIT"
},
"node_modules/vue": {
- "version": "3.5.40",
- "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.40.tgz",
- "integrity": "sha512-+8PJ4SJXdn/cHGImF4CKdxlWHIN5Dkt7DoufRREM6h6uVCx2m7QxgcEQmmzyOK8A9mcafg7sFbJFYsdFVubTig==",
+ "version": "3.5.43",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.43.tgz",
+ "integrity": "sha512-o5qZoksdnjIKvW1srZ3ab7pcDNYAerBjRe54D0LBLfRdCYFrSgBHVXokMas35czQc0//lmx4/tuY4ZNQ+Rf2Ng==",
"license": "MIT",
"dependencies": {
- "@vue/compiler-dom": "3.5.40",
- "@vue/compiler-sfc": "3.5.40",
- "@vue/runtime-dom": "3.5.40",
- "@vue/server-renderer": "3.5.40",
- "@vue/shared": "3.5.40"
+ "@vue/compiler-dom": "3.5.43",
+ "@vue/compiler-sfc": "3.5.43",
+ "@vue/runtime-dom": "3.5.43",
+ "@vue/server-renderer": "3.5.43",
+ "@vue/shared": "3.5.43"
},
"peerDependencies": {
"typescript": "*"
diff --git a/examples/WebApp/InfiniFrameExample.WebApp.Vue/Source/InfiniFrame.Vue/package.json b/examples/WebApp/InfiniFrameExample.WebApp.Vue/Source/InfiniFrame.Vue/package.json
index b2205f4f7..44aefbe9f 100644
--- a/examples/WebApp/InfiniFrameExample.WebApp.Vue/Source/InfiniFrame.Vue/package.json
+++ b/examples/WebApp/InfiniFrameExample.WebApp.Vue/Source/InfiniFrame.Vue/package.json
@@ -9,14 +9,14 @@
"vue.preview": "vite preview"
},
"dependencies": {
- "vue": "^3.5.40"
+ "vue": "^3.5.43"
},
"devDependencies": {
- "@types/node": "^26.2.0",
- "@vitejs/plugin-vue": "^6.0.8",
+ "@types/node": "^26.6.2",
+ "@vitejs/plugin-vue": "^6.0.9",
"@vue/tsconfig": "^0.9.1",
"typescript": "^6.0.3",
- "vite": "^8.2.2",
+ "vite": "^8.3.0",
"vue-tsc": "^3.3.11"
},
"ncu": {
diff --git a/examples/WebApp/InfiniFrameExample.WebApp/Program.cs b/examples/WebApp/InfiniFrameExample.WebApp/Program.cs
index 8de617cc8..4de4afb2c 100644
--- a/examples/WebApp/InfiniFrameExample.WebApp/Program.cs
+++ b/examples/WebApp/InfiniFrameExample.WebApp/Program.cs
@@ -2,6 +2,7 @@
// Imports
// ---------------------------------------------------------------------------------------------------------------------
using InfiniFrame;
+using InfiniFrame.Application;
using InfiniFrame.WebServer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
@@ -14,23 +15,20 @@ namespace InfiniFrameExample.WebApp;
public static class Program {
[STAThread]
public static void Main(string[] args) {
- InfiniFrameWebApplicationBuilder builder =
- InfiniFrameWebApplication.CreateBuilder(args);
-
- builder.WebApp.WebHost.UseUrls("http://127.0.0.1:5055");
- builder.WindowBuilder
- .SetStartPageUrl("http://127.0.0.1:5055")
- .SetTitle("InfiniFrame WebServer Repro")
- .SetIconFile("wwwroot/favicon.ico");
-
- InfiniFrameWebApplication app = builder.Build();
- app.UseAutoServerClose();
-
- app.WebApp.MapGet("/", handler: () => Results.Content(
- "InfiniFrame loaded",
- "text/html"
- ));
+ InfiniFrameApplication app = InfiniFrameApplication.CreateBuilder(args)
+ .WithWindow("web", configure: window => window
+ .SetStartPageUrl("http://127.0.0.1:5055")
+ .SetTitle("InfiniFrame WebServer Repro")
+ .SetIconFile("wwwroot/favicon.ico"))
+ .UseWebServer("web", configure: builder => {
+ builder.WebHost.UseUrls("http://127.0.0.1:5055");
+ builder.ConfigureWebApplication(webApp => webApp.MapGet("/", handler: () => Results.Content(
+ "InfiniFrame loaded",
+ "text/html"
+ )));
+ })
+ .Build();
app.Run();
}
-}
\ No newline at end of file
+}
diff --git a/examples/WebApp/InfiniFrameExample.WebApp/README.md b/examples/WebApp/InfiniFrameExample.WebApp/README.md
index 6c1c51fa6..67eb03dfa 100644
--- a/examples/WebApp/InfiniFrameExample.WebApp/README.md
+++ b/examples/WebApp/InfiniFrameExample.WebApp/README.md
@@ -4,9 +4,9 @@ Demonstrates InfiniFrame's built-in ASP.NET Core web server integration. A local
## What It Shows
-- `InfiniFrameWebApplicationBuilder` and `InfiniFrameWebApplication` API
+- `InfiniFrameApplicationBuilder` and `InfiniFrameApplication` API
- ASP.NET Core minimal APIs (`MapGet`)
-- `UseAutoServerClose()` for graceful shutdown
+- Application-owned lifecycle for graceful shutdown
- Window-to-server DI integration
## Run
diff --git a/scripts/clean.ps1 b/scripts/clean.ps1
index a9d2913bf..d6d4fe45a 100644
--- a/scripts/clean.ps1
+++ b/scripts/clean.ps1
@@ -24,10 +24,6 @@ $ExtraPaths = @(
"../src/InfiniFrame.NativeBridge/build",
"../src/InfiniFrame.NativeBridge/artifacts",
"../src/InfiniFrame.Js/node_modules",
- "../src/InfiniFrame.NativeBridge/Native/cmake-build-debug-linux",
- "../src/InfiniFrame.NativeBridge/Native/cmake-build-debug-windows",
- "../src/InfiniFrame.NativeBridge/Native/cmake-build-release-linux",
- "../src/InfiniFrame.NativeBridge/Native/cmake-build-release-windows",
"../src/InfiniFrame.NativeBridge/Native/packages"
)
@@ -40,4 +36,4 @@ foreach ($RelativePath in $ExtraPaths) {
}
}
-Write-Host "Done cleaning bin/obj folders and extra build artifacts."
\ No newline at end of file
+Write-Host "Done cleaning bin/obj folders and extra build artifacts."
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index 90f2fe50f..ad07e243e 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -40,16 +40,21 @@
+
+
+
+
-
+
+
@@ -77,4 +82,4 @@
-
\ No newline at end of file
+
diff --git a/src/InfiniFrame.Application/ApplicationConfiguration.cs b/src/InfiniFrame.Application/ApplicationConfiguration.cs
new file mode 100644
index 000000000..058f2a515
--- /dev/null
+++ b/src/InfiniFrame.Application/ApplicationConfiguration.cs
@@ -0,0 +1,22 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+using InfiniFrame.NativeBridge.Parameters.Application;
+
+namespace InfiniFrame.Application;
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+internal sealed record ApplicationConfiguration(
+ string? WebView2RuntimePath = null,
+ string? NotificationRegistrationId = null,
+ string? AppUserModelId = null,
+ string? DefaultNotificationIcon = null
+) {
+ internal InfiniFrameNativeApplicationParameters ToNativeParameters() => new() {
+ WebView2RuntimePath = WebView2RuntimePath,
+ NotificationRegistrationId = NotificationRegistrationId,
+ AppUserModelId = AppUserModelId,
+ DefaultNotificationIcon = DefaultNotificationIcon
+ };
+}
diff --git a/src/InfiniFrame.Application/InfiniFrame.Application.csproj b/src/InfiniFrame.Application/InfiniFrame.Application.csproj
new file mode 100644
index 000000000..1f6959886
--- /dev/null
+++ b/src/InfiniFrame.Application/InfiniFrame.Application.csproj
@@ -0,0 +1,17 @@
+
+
+ InfiniLore.InfiniFrame.Application
+ InfiniFrame application lifecycle and window orchestration.
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/InfiniFrame.Application/InfiniFrameApplication.cs b/src/InfiniFrame.Application/InfiniFrameApplication.cs
new file mode 100644
index 000000000..ba3bcea99
--- /dev/null
+++ b/src/InfiniFrame.Application/InfiniFrameApplication.cs
@@ -0,0 +1,659 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+using InfiniFrame.NativeBridge;
+using InfiniFrame.NativeBridge.Handles;
+using FluentValidation;
+using InfiniFrame.NativeBridge.Parameters.Application;
+using InfiniFrame.Utilities;
+using InfiniFrame.Window.Builder;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+
+namespace InfiniFrame.Application;
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+///
+/// Application-level owner for lazily built InfiniFrame windows.
+///
+public sealed class InfiniFrameApplication : IInfiniFrameApplication {
+ private readonly ILogger logger;
+ private readonly NativeApplicationHandle _nativeHandle;
+
+ #if NET9_0_OR_GREATER
+ private readonly Lock _gate = new();
+ #else
+ // ReSharper disable once ChangeFieldTypeToSystemThreadingLock
+ private readonly object _gate = new();
+ #endif
+
+ private readonly List<(
+ string? Id,
+ Action? Configure,
+ InfiniFrameWindowBuilder? Builder,
+ IServiceProvider? Provider
+ )> _registrations = [];
+ private readonly List> _shutdownActions = [];
+ private readonly List> _startupActions = [];
+ private readonly List _naturalWindowTeardowns = [];
+ private readonly Dictionary _windows = [];
+ private IServiceProvider? _serviceProvider;
+ private int _disposed;
+ private int _runState;
+ private bool _built;
+ private int _shutdownRequested;
+
+ ///
+ public IReadOnlyList Windows {
+ get {
+ lock (_gate) return _windows.Values.ToArray();
+ }
+ }
+
+ ///
+ public IReadOnlyList WindowRegistrations {
+ get {
+ lock (_gate) return _registrations.Select(r => r.Id).ToArray();
+ }
+ }
+
+
+ private InfiniFrameApplication(ILogger logger, ApplicationConfiguration configuration) {
+ this.logger = logger;
+ InfiniFrameNativeApplicationParameters parameters = configuration.ToNativeParameters();
+ new InfiniFrameNativeApplicationParametersValidator().ValidateAndThrow(parameters);
+
+ InfiniFrameNativeInteropStatus status = InfiniFrameNative.ApplicationConstructor(out IntPtr handle);
+ if (status != InfiniFrameNativeInteropStatus.Success)
+ throw new InfiniFrameNativeInteropException(InfiniFrameNative.GetLastErrorMessage() ?? "Could not create native application.");
+
+ _nativeHandle = new NativeApplicationHandle(handle);
+ InfiniFrameNativeInteropStatus configureStatus = InfiniFrameNative.ApplicationConfigure(
+ _nativeHandle.DangerousGetHandle(),
+ in parameters
+ );
+
+ if (configureStatus == InfiniFrameNativeInteropStatus.Success) return;
+
+ _nativeHandle.Dispose();
+ throw new InfiniFrameNativeInteropException(
+ InfiniFrameNative.GetLastErrorMessage() ?? "Could not configure native application.");
+ }
+
+ /// Creates an application without requiring a dependency-injection container.
+ public static InfiniFrameApplication Initialize()
+ => new(NullLogger.Instance, new ApplicationConfiguration());
+
+ internal static InfiniFrameApplication Initialize(ApplicationConfiguration configuration)
+ => new(NullLogger.Instance, configuration);
+
+ public static InfiniFrameApplicationBuilder CreateBuilder(string[]? args = null)
+ => new(args);
+
+ /// Creates an application using the supplied logger.
+ public static InfiniFrameApplication Initialize(ILogger logger) {
+ ArgumentNullException.ThrowIfNull(logger);
+ return new InfiniFrameApplication(logger, new ApplicationConfiguration());
+ }
+
+ public Guid Id { get; } = Guid.NewGuid();
+ public IntPtr ApplicationHandle => _nativeHandle.DangerousGetHandle();
+ public bool IsShutdownRequested => Volatile.Read(ref _shutdownRequested) != 0;
+ public event Action? WindowCreated;
+ public event Action? WindowDestroyed;
+
+ ///
+ public void RegisterWindow(Action configure) {
+ ArgumentNullException.ThrowIfNull(configure);
+ RegisterWindowCore(null, configure, null);
+ }
+
+ ///
+ public void RegisterWindow(string id, Action configure) {
+ ArgumentException.ThrowIfNullOrWhiteSpace(id);
+ ArgumentNullException.ThrowIfNull(configure);
+ RegisterWindowCore(id, configure, null);
+ }
+
+ /// Registers an unnamed window and returns this application for fluent configuration.
+ public InfiniFrameApplication WithWindow(Action configure) {
+ RegisterWindow(configure);
+ return this;
+ }
+
+ /// Registers a named window and returns this application for fluent configuration.
+ public InfiniFrameApplication WithWindow(string id, Action configure) {
+ RegisterWindow(id, configure);
+ return this;
+ }
+
+ ///
+ public IInfiniFrameWindow GetWindow(string id) {
+ ArgumentException.ThrowIfNullOrWhiteSpace(id);
+ lock (_gate) {
+ EnsureBuilt();
+ return _windows.TryGetValue(id, out IInfiniFrameWindow? window)
+ ? window
+ : throw new KeyNotFoundException($"Window with id '{id}' was not found.");
+ }
+ }
+
+ ///
+ public IInfiniFrameWindow? TryGetWindow(string id) {
+ ArgumentException.ThrowIfNullOrWhiteSpace(id);
+ lock (_gate) return _built && _windows.TryGetValue(id, out IInfiniFrameWindow? window) ? window : null;
+ }
+
+ ///
+ public void Run() {
+ BeginRun();
+ try {
+ EnsureWindowsStaThread();
+ if (HasRegisteredWindows() || OperatingSystem.IsMacOS())
+ RegisterNativeApplication();
+ if (IsShutdownRequested) return;
+ StartRegisteredComponents();
+ BuildAllWindows();
+ RunNativeLoop();
+ logger.LogDebug("Native application loop returned");
+ }
+ finally {
+ Dispose();
+ }
+ }
+
+ ///
+ public async Task RunAsync(CancellationToken ct = default) {
+ BeginRun();
+ await using CancellationTokenRegistration registration = ct.Register(Shutdown);
+ Task? uiTask = null;
+ try {
+ ct.ThrowIfCancellationRequested();
+ await StartRegisteredComponentsAsync().ConfigureAwait(false);
+ ct.ThrowIfCancellationRequested();
+
+ if (OperatingSystem.IsMacOS()) {
+ // AppKit and WKWebView require process-main-thread ownership.
+ // Register and build windows on the calling thread (the main thread),
+ // then run the blocking native wait asynchronously. The native Run()
+ // implementation keeps AppKit work on the main run loop while the
+ // managed caller remains free to request shutdown.
+ var completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ try {
+ if (IsShutdownRequested) {
+ completion.TrySetResult();
+ } else {
+ if (HasRegisteredWindows())
+ RegisterNativeApplication();
+ if (IsShutdownRequested) {
+ completion.TrySetResult();
+ } else {
+ BuildAllWindows();
+ if (IsShutdownRequested)
+ CloseAll();
+
+ var uiThread = new Thread(() => {
+ try {
+ RunNativeLoop();
+ completion.TrySetResult();
+ }
+ catch (Exception exception) when (ExceptionsUtility.IsNonFatalException(exception)) {
+ completion.TrySetException(exception);
+ }
+ }) {
+ IsBackground = true,
+ Name = "InfiniFrame Application macOS Native Loop"
+ };
+ uiThread.Start();
+ }
+ }
+ }
+ catch (Exception exception) when (ExceptionsUtility.IsNonFatalException(exception)) {
+ completion.TrySetException(exception);
+ }
+
+ uiTask = completion.Task;
+ await uiTask.ConfigureAwait(false);
+ } else {
+ var completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ var uiThread = new Thread(() => {
+ try {
+ if (IsShutdownRequested) {
+ completion.TrySetResult();
+ return;
+ }
+
+ if (HasRegisteredWindows())
+ RegisterNativeApplication();
+ if (IsShutdownRequested) {
+ completion.TrySetResult();
+ return;
+ }
+
+ BuildAllWindows();
+ if (IsShutdownRequested) {
+ CloseAll();
+ }
+
+ RunNativeLoop();
+ completion.TrySetResult();
+ }
+ catch (Exception exception) when (ExceptionsUtility.IsNonFatalException(exception)) {
+ completion.TrySetException(exception);
+ }
+ }) {
+ IsBackground = true,
+ Name = "InfiniFrame Application UI Thread"
+ };
+
+ if (OperatingSystem.IsWindows())
+ uiThread.SetApartmentState(ApartmentState.STA);
+ uiThread.Start();
+ uiTask = completion.Task;
+ await uiTask.ConfigureAwait(false);
+ }
+ }
+ finally {
+ try {
+ if (uiTask is not null)
+ await uiTask.ConfigureAwait(false);
+ }
+ finally {
+ await DisposeAsync().ConfigureAwait(false);
+ }
+ }
+ }
+
+ ///
+ public void Shutdown() {
+ if (Volatile.Read(ref _disposed) != 0) return;
+ if (Interlocked.Exchange(ref _shutdownRequested, 1) != 0) return;
+ InfiniFrameNative.ApplicationShutdown(_nativeHandle.DangerousGetHandle());
+ CloseAll();
+ }
+
+ ///
+ public void CloseAll() {
+ foreach (IInfiniFrameWindow window in Windows.ToArray()) {
+ try {
+ window.Close();
+ }
+ catch (Exception exception) when (exception is ObjectDisposedException or InvalidOperationException) {
+ logger.LogDebug(exception, "Window was already unavailable during application shutdown");
+ }
+ }
+ }
+
+ private void RegisterNativeApplication() {
+ InfiniFrameNativeInteropStatus status = InfiniFrameNative.ApplicationRegister(_nativeHandle.DangerousGetHandle());
+ if (status != InfiniFrameNativeInteropStatus.Success)
+ throw new InfiniFrameNativeInteropException(
+ InfiniFrameNative.GetLastErrorMessage() ?? "Could not register native application.");
+ }
+
+ private bool HasRegisteredWindows() {
+ lock (_gate) return _registrations.Count != 0;
+ }
+
+ ///
+ public void Dispose() {
+ if (Interlocked.Exchange(ref _disposed, 1) != 0) return;
+
+ IInfiniFrameWindow[] windows;
+ lock (_gate) {
+ windows = _windows.Values.ToArray();
+ }
+
+ Exception? windowDisposalFailure = null;
+ foreach (IInfiniFrameWindow window in windows) {
+ try {
+ (window as IDisposable)?.Dispose();
+ if (window.LifecycleState != InfiniFrameWindowLifecycleState.Disposed)
+ throw new InvalidOperationException("A window did not complete native disposal.");
+ RemoveTrackedWindow(window);
+ }
+ catch (Exception ex) when (ex is not OutOfMemoryException and not StackOverflowException) {
+ logger.LogWarning(ex, "Failed to dispose an application window");
+ windowDisposalFailure ??= ex;
+ }
+ }
+ DrainNaturalWindowTeardowns();
+ logger.LogDebug("Application window disposal completed");
+ if (windowDisposalFailure is not null) {
+ Volatile.Write(ref _disposed, 0);
+ throw new InvalidOperationException("The application could not dispose all windows.", windowDisposalFailure);
+ }
+ lock (_gate) {
+ _windows.Clear();
+ _registrations.Clear();
+ }
+ ReleaseNativeHandleOrThrow();
+ logger.LogDebug("Application native disposal completed");
+ StopRegisteredComponents();
+ if (_serviceProvider is IAsyncDisposable asyncServiceProvider)
+ asyncServiceProvider.DisposeAsync().AsTask().GetAwaiter().GetResult();
+ else (_serviceProvider as IDisposable)?.Dispose();
+ _serviceProvider = null;
+ }
+
+ ///
+ public async ValueTask DisposeAsync() {
+ if (Interlocked.Exchange(ref _disposed, 1) != 0) return;
+
+ IInfiniFrameWindow[] windows;
+ lock (_gate) {
+ windows = _windows.Values.ToArray();
+ }
+
+ Exception? windowDisposalFailure = null;
+ foreach (IInfiniFrameWindow window in windows) {
+ try {
+ if (window is IAsyncDisposable asyncDisposable)
+ await asyncDisposable.DisposeAsync().ConfigureAwait(false);
+ else (window as IDisposable)?.Dispose();
+ if (window.LifecycleState != InfiniFrameWindowLifecycleState.Disposed)
+ throw new InvalidOperationException("A window did not complete native disposal.");
+ RemoveTrackedWindow(window);
+ }
+ catch (Exception ex) when (ex is not OutOfMemoryException and not StackOverflowException) {
+ logger.LogWarning(ex, "Failed to asynchronously dispose an application window");
+ windowDisposalFailure ??= ex;
+ }
+ }
+ await DrainNaturalWindowTeardownsAsync().ConfigureAwait(false);
+ if (windowDisposalFailure is not null) {
+ Volatile.Write(ref _disposed, 0);
+ throw new InvalidOperationException("The application could not dispose all windows.", windowDisposalFailure);
+ }
+ lock (_gate) {
+ _windows.Clear();
+ _registrations.Clear();
+ }
+ ReleaseNativeHandleOrThrow();
+ await StopRegisteredComponentsAsync().ConfigureAwait(false);
+ if (_serviceProvider is IAsyncDisposable asyncServiceProvider)
+ await asyncServiceProvider.DisposeAsync().ConfigureAwait(false);
+ else (_serviceProvider as IDisposable)?.Dispose();
+ _serviceProvider = null;
+ }
+
+ internal void RegisterWindowBuilder(string id, InfiniFrameWindowBuilder builder, IServiceProvider? provider = null) {
+ ArgumentException.ThrowIfNullOrWhiteSpace(id);
+ ArgumentNullException.ThrowIfNull(builder);
+ RegisterWindowCore(id, null, builder, provider);
+ }
+
+ private void BeginRun() {
+ ObjectDisposedException.ThrowIf(Volatile.Read(ref _disposed) != 0, this);
+ if (Interlocked.CompareExchange(ref _runState, 1, 0) != 0)
+ throw new InvalidOperationException("The InfiniFrame application can only be run once.");
+ }
+
+ internal void ValidateWindowIntegrationTargets(IReadOnlyList? ids, string integrationName) {
+ lock (_gate) {
+ GetIntegrationTargets(ids, integrationName);
+ }
+ }
+
+ internal void ApplyWindowIntegration(
+ IReadOnlyList? ids,
+ string integrationName,
+ Action configure
+ ) {
+ ArgumentNullException.ThrowIfNull(configure);
+ lock (_gate) {
+ foreach (int index in GetIntegrationTargets(ids, integrationName)) {
+ (string? id, Action? existing, InfiniFrameWindowBuilder? builder, IServiceProvider? provider) = _registrations[index];
+ _registrations[index] = (id, target => {
+ existing?.Invoke(target);
+ configure(target);
+ }, builder, provider);
+ }
+ }
+ }
+
+ internal void RegisterShutdownAction(Func action) {
+ ArgumentNullException.ThrowIfNull(action);
+ lock (_gate) _shutdownActions.Add(action);
+ }
+
+ internal void RegisterStartupAction(Func action) {
+ ArgumentNullException.ThrowIfNull(action);
+ lock (_gate) _startupActions.Add(action);
+ }
+
+ private void RegisterWindowCore(
+ string? id,
+ Action? configure,
+ InfiniFrameWindowBuilder? builder,
+ IServiceProvider? provider = null
+ ) {
+ ObjectDisposedException.ThrowIf(Volatile.Read(ref _disposed) != 0, this);
+ lock (_gate) {
+ if (_built || Volatile.Read(ref _runState) != 0)
+ throw new InvalidOperationException("Cannot register windows after the application run has started.");
+ if (id is not null && _registrations.Any(registration => registration.Id == id))
+ throw new ArgumentException($"A window with id '{id}' is already registered.", nameof(id));
+ _registrations.Add((id, configure, builder, provider));
+ }
+ }
+
+ private IReadOnlyList GetIntegrationTargets(IReadOnlyList? ids, string integrationName) {
+ if (ids is null || ids.Count == 0) {
+ if (_registrations.Count == 0)
+ throw new InvalidOperationException(
+ $"Cannot bind {integrationName}: no windows are registered. Register a window before configuring the integration.");
+ if (_registrations.Count != 1)
+ throw new InvalidOperationException(
+ $"Cannot bind {integrationName}: {_registrations.Count} windows are registered. Specify explicit window IDs.");
+ return [0];
+ }
+
+ if (ids.Any(string.IsNullOrWhiteSpace))
+ throw new ArgumentException($"{integrationName} window IDs must not be null or whitespace.", nameof(ids));
+ if (ids.Count != ids.Distinct(StringComparer.Ordinal).Count())
+ throw new ArgumentException($"{integrationName} window IDs must not contain duplicates.", nameof(ids));
+
+ var targets = new List(ids.Count);
+ foreach (string id in ids) {
+ int index = _registrations.FindIndex(registration => string.Equals(registration.Id, id, StringComparison.Ordinal));
+ if (index < 0)
+ throw new InvalidOperationException(
+ $"Cannot bind {integrationName}: no registered window has ID '{id}'.");
+ targets.Add(index);
+ }
+ return targets;
+ }
+
+ private void BuildAllWindows() {
+ (string Id, IInfiniFrameWindow Window)[] built;
+ lock (_gate) {
+ ObjectDisposedException.ThrowIf(Volatile.Read(ref _disposed) != 0, this);
+ if (_built) return;
+
+ var windows = new List<(string Id, IInfiniFrameWindow Window)>();
+ try {
+ foreach ((string? id, Action? configure, InfiniFrameWindowBuilder? registeredBuilder, IServiceProvider? provider) in _registrations) {
+ InfiniFrameWindowBuilder builder = registeredBuilder ?? new InfiniFrameWindowBuilder();
+ configure?.Invoke(builder);
+ builder.SetApplicationHandle(_nativeHandle.DangerousGetHandle());
+ string windowId = id ?? Guid.NewGuid().ToString("N");
+ windows.Add((windowId, builder.Build(provider ?? _serviceProvider)));
+ }
+
+ foreach ((string id, IInfiniFrameWindow window) in windows) {
+ _windows.Add(id, window);
+ }
+ _registrations.Clear();
+ _built = true;
+ built = [.. windows];
+ }
+ catch {
+ foreach ((_, IInfiniFrameWindow window) in windows) (window as IDisposable)?.Dispose();
+ throw;
+ }
+ }
+
+ foreach ((_, IInfiniFrameWindow window) in built)
+ WindowCreated?.Invoke(window);
+ foreach ((string id, IInfiniFrameWindow window) in built)
+ lock (_gate) _naturalWindowTeardowns.Add(TrackNaturalWindowCloseAsync(id, window));
+ }
+
+ private async Task TrackNaturalWindowCloseAsync(string id, IInfiniFrameWindow window) {
+ try {
+ await window.Features.Lifecycle.WaitForTeardownAsync().ConfigureAwait(false);
+ if (window is IAsyncDisposable asyncDisposable)
+ await asyncDisposable.DisposeAsync().ConfigureAwait(false);
+ else (window as IDisposable)?.Dispose();
+ RemoveTrackedWindow(id, window);
+ }
+ catch (Exception exception) when (exception is not OutOfMemoryException and not StackOverflowException) {
+ logger.LogDebug(exception, "Could not finalize a naturally closed application window");
+ }
+ }
+
+ private void RemoveTrackedWindow(IInfiniFrameWindow window) {
+ string? id;
+ lock (_gate) {
+ id = _windows.FirstOrDefault(pair => ReferenceEquals(pair.Value, window)).Key;
+ if (id is null) return;
+ _windows.Remove(id);
+ }
+ WindowDestroyed?.Invoke(window);
+ }
+
+ private void RemoveTrackedWindow(string id, IInfiniFrameWindow window) {
+ lock (_gate) {
+ if (!_windows.TryGetValue(id, out IInfiniFrameWindow? tracked) || !ReferenceEquals(tracked, window)) return;
+ _windows.Remove(id);
+ }
+ WindowDestroyed?.Invoke(window);
+ }
+
+ private void DrainNaturalWindowTeardowns() {
+ Task[] teardowns;
+ lock (_gate) {
+ teardowns = _naturalWindowTeardowns.ToArray();
+ _naturalWindowTeardowns.Clear();
+ }
+
+ foreach (Task teardown in teardowns) teardown.GetAwaiter().GetResult();
+ }
+
+ private async Task DrainNaturalWindowTeardownsAsync() {
+ Task[] teardowns;
+ lock (_gate) {
+ teardowns = _naturalWindowTeardowns.ToArray();
+ _naturalWindowTeardowns.Clear();
+ }
+
+ await Task.WhenAll(teardowns).ConfigureAwait(false);
+ }
+
+ private void EnsureBuilt()
+ {
+ ObjectDisposedException.ThrowIf(Volatile.Read(ref _disposed) != 0, this);
+ if (!_built) throw new InvalidOperationException("Windows have not been built yet. Call Run() or RunAsync() first.");
+ }
+
+ private void RunNativeLoop() {
+ if (OperatingSystem.IsWindows()) {
+ InfiniFrameNativeInteropStatus status = InfiniFrameNative.ApplicationRun(_nativeHandle.DangerousGetHandle());
+ if (status != InfiniFrameNativeInteropStatus.Success)
+ throw new InfiniFrameNativeInteropException(
+ InfiniFrameNative.GetLastErrorMessage() ?? "Could not run the native application.");
+
+ return;
+ }
+
+ InfiniFrameNativeInteropStatus linuxStatus = InfiniFrameNative.ApplicationRun(_nativeHandle.DangerousGetHandle());
+ if (linuxStatus != InfiniFrameNativeInteropStatus.Success)
+ throw new InfiniFrameNativeInteropException(
+ InfiniFrameNative.GetLastErrorMessage() ?? "Could not run the native application.");
+ }
+
+ private static void EnsureWindowsStaThread() {
+ if (OperatingSystem.IsWindows() && Thread.CurrentThread.GetApartmentState() != ApartmentState.STA)
+ throw new InvalidOperationException("InfiniFrameApplication.Run() must be called from a Windows STA thread.");
+ }
+
+ internal void AttachServiceProvider(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider;
+ internal IServiceProvider RootServiceProvider => _serviceProvider
+ ?? throw new InvalidOperationException("The application service provider has not been initialized.");
+
+ private void StopRegisteredComponents() {
+ Func[] actions;
+ lock (_gate) {
+ actions = _shutdownActions.ToArray();
+ _shutdownActions.Clear();
+ }
+
+ foreach (Func action in actions) {
+ try { action().GetAwaiter().GetResult(); }
+ catch (Exception ex) when (ex is not OutOfMemoryException and not StackOverflowException) {
+ logger.LogWarning(ex, "Failed to stop an application component");
+ }
+ }
+ }
+
+ private void StartRegisteredComponents() {
+ Func[] actions;
+ lock (_gate) actions = _startupActions.ToArray();
+ foreach (Func action in actions) action().GetAwaiter().GetResult();
+ }
+
+ private async Task StartRegisteredComponentsAsync() {
+ Func[] actions;
+ lock (_gate) actions = _startupActions.ToArray();
+ foreach (Func action in actions) await action().ConfigureAwait(false);
+ }
+
+ private async Task StopRegisteredComponentsAsync() {
+ Func[] actions;
+ lock (_gate) {
+ actions = _shutdownActions.ToArray();
+ _shutdownActions.Clear();
+ }
+
+ foreach (Func action in actions) {
+ try { await action().ConfigureAwait(false); }
+ catch (Exception ex) when (ex is not OutOfMemoryException and not StackOverflowException) {
+ logger.LogWarning(ex, "Failed to stop an application component");
+ }
+ }
+ }
+
+ internal void ApplyWindowIntegrationToAll(
+ Action configure
+ ) {
+ ArgumentNullException.ThrowIfNull(configure);
+ lock (_gate) {
+ for (int index = 0; index < _registrations.Count; index++) {
+ (string? id, Action? existing, InfiniFrameWindowBuilder? builder, IServiceProvider? provider) = _registrations[index];
+ _registrations[index] = (id, target => {
+ existing?.Invoke(target);
+ configure(target);
+ }, builder, provider);
+ }
+ }
+ }
+
+ private void EnsureNativeHandleReleased() {
+ if (_nativeHandle.ReleaseStatus == InfiniFrameNativeInteropStatus.Success) return;
+
+ _ = InfiniFrameNative.ApplicationGetWindowCount(_nativeHandle.DangerousGetHandle(), out nuint nativeWindowCount);
+ throw new InfiniFrameNativeInteropException(
+ $"Could not destroy the native application ({_nativeHandle.ReleaseStatus}); " +
+ $"managed windows remaining: {Windows.Count}, native windows remaining: {nativeWindowCount}. " +
+ (InfiniFrameNative.GetLastErrorMessage() ?? "No native error message provided."));
+ }
+
+ private void ReleaseNativeHandleOrThrow() {
+ if (_nativeHandle.TryRelease()) return;
+
+ Volatile.Write(ref _disposed, 0);
+ EnsureNativeHandleReleased();
+ }
+}
diff --git a/src/InfiniFrame.Application/InfiniFrameApplicationBuilder.cs b/src/InfiniFrame.Application/InfiniFrameApplicationBuilder.cs
new file mode 100644
index 000000000..8923cd28c
--- /dev/null
+++ b/src/InfiniFrame.Application/InfiniFrameApplicationBuilder.cs
@@ -0,0 +1,110 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+using InfiniFrame.Window;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace InfiniFrame.Application;
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+/// Composes an InfiniFrame application before it is built.
+public sealed class InfiniFrameApplicationBuilder {
+ private readonly List> _integrations = [];
+ private readonly List<(string? Id, Action Configure)> _windows = [];
+ private string? _webView2RuntimePath;
+ private string? _notificationRegistrationId;
+ private string? _appUserModelId;
+ private string? _defaultNotificationIcon;
+
+ public IReadOnlyCollection<(string? Id, Action Configure)> WindowRegistrations
+ => _windows.AsReadOnly();
+
+ // -----------------------------------------------------------------------------------------------------------------
+ // Methods
+ // -----------------------------------------------------------------------------------------------------------------
+ internal InfiniFrameApplicationBuilder(string[]? args) {
+ Args = args ?? [];
+ Services = new ServiceCollection().AddLogging().AddInfiniFrame();
+ }
+
+ internal string[] Args { get; }
+ public IServiceCollection Services { get; }
+
+ public InfiniFrameApplicationBuilder WithWindow(Action configure) {
+ ArgumentNullException.ThrowIfNull(configure);
+ _windows.Add((null, configure));
+ return this;
+ }
+
+ public InfiniFrameApplicationBuilder WithWindow(string id, Action configure) {
+ ArgumentException.ThrowIfNullOrWhiteSpace(id);
+ ArgumentNullException.ThrowIfNull(configure);
+ if (_windows.Any(window => string.Equals(window.Id, id, StringComparison.Ordinal)))
+ throw new ArgumentException($"A window with id '{id}' is already registered.", nameof(id));
+ _windows.Add((id, configure));
+ return this;
+ }
+
+ public InfiniFrameApplicationBuilder WithWebView2RuntimePath(string path) {
+ ArgumentException.ThrowIfNullOrWhiteSpace(path);
+ _webView2RuntimePath = Path.GetFullPath(path);
+ return this;
+ }
+
+ public InfiniFrameApplicationBuilder WithNotificationRegistrationId(string id) {
+ ArgumentException.ThrowIfNullOrWhiteSpace(id);
+ _notificationRegistrationId = id;
+ return this;
+ }
+
+ public InfiniFrameApplicationBuilder WithAppUserModelId(string id) {
+ ArgumentException.ThrowIfNullOrWhiteSpace(id);
+ _appUserModelId = id;
+ return this;
+ }
+
+ public InfiniFrameApplicationBuilder WithDefaultNotificationIcon(string path) {
+ ArgumentException.ThrowIfNullOrWhiteSpace(path);
+ _defaultNotificationIcon = Path.GetFullPath(path);
+ return this;
+ }
+
+ internal void AddIntegration(Action integration) {
+ ArgumentNullException.ThrowIfNull(integration);
+ _integrations.Add(integration);
+ }
+
+ public InfiniFrameApplication Build() {
+ InfiniFrameApplication? application = null;
+ IServiceProvider? serviceProvider = null;
+ bool serviceProviderAttached = false;
+ try {
+ application = InfiniFrameApplication.Initialize(new ApplicationConfiguration(
+ _webView2RuntimePath,
+ _notificationRegistrationId,
+ _appUserModelId,
+ _defaultNotificationIcon
+ ));
+
+ Services.AddSingleton(application);
+ serviceProvider = Services.BuildServiceProvider();
+
+ foreach ((string? id, Action configure) in _windows) {
+ if (id is null) application.RegisterWindow(configure);
+ else application.RegisterWindow(id, configure);
+ }
+ application.AttachServiceProvider(serviceProvider);
+ serviceProviderAttached = true;
+ foreach (Action integration in _integrations)
+ integration(application);
+ return application;
+ }
+ catch {
+ application?.Dispose();
+ if (!serviceProviderAttached && serviceProvider is IDisposable disposableServiceProvider)
+ disposableServiceProvider.Dispose();
+ throw;
+ }
+ }
+}
diff --git a/src/InfiniFrame.Blazor/InfiniFrame.Blazor.csproj b/src/InfiniFrame.Blazor/InfiniFrame.Blazor.csproj
index 78d1df882..157d0a13d 100644
--- a/src/InfiniFrame.Blazor/InfiniFrame.Blazor.csproj
+++ b/src/InfiniFrame.Blazor/InfiniFrame.Blazor.csproj
@@ -1,12 +1,10 @@
InfiniLore.InfiniFrame.Blazor
- Library
Pre-built Razor components for custom window chrome in InfiniFrame Blazor applications. Includes drag areas, window buttons, and resize thumbs.
-
diff --git a/src/InfiniFrame.BlazorWebView/FileProviders/StaticWebAssetsRuntimeFileProvider.cs b/src/InfiniFrame.BlazorWebView/FileProviders/StaticWebAssetsRuntimeFileProvider.cs
index e5170b595..ed36422af 100644
--- a/src/InfiniFrame.BlazorWebView/FileProviders/StaticWebAssetsRuntimeFileProvider.cs
+++ b/src/InfiniFrame.BlazorWebView/FileProviders/StaticWebAssetsRuntimeFileProvider.cs
@@ -402,15 +402,14 @@ private static string NormalizeSubPath(string? subPath) {
string fileName = Path.GetFileName(originalPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar));
if (string.IsNullOrWhiteSpace(fileName)) return null;
- string searchPattern = fileName;
string? directory = baseDirectory;
while (!string.IsNullOrEmpty(directory)) {
- string candidate = Path.Join(directory, searchPattern);
+ string candidate = Path.Join(directory, fileName);
if (Directory.Exists(candidate)) return candidate;
string[] children = [];
try {
- children = Directory.GetDirectories(directory, searchPattern, SearchOption.TopDirectoryOnly);
+ children = Directory.GetDirectories(directory, fileName, SearchOption.TopDirectoryOnly);
}
catch (IOException) {
// Ignore
diff --git a/src/InfiniFrame.BlazorWebView/InfiniFrame.BlazorWebView.csproj b/src/InfiniFrame.BlazorWebView/InfiniFrame.BlazorWebView.csproj
index 3c9df2d66..8b15730ab 100644
--- a/src/InfiniFrame.BlazorWebView/InfiniFrame.BlazorWebView.csproj
+++ b/src/InfiniFrame.BlazorWebView/InfiniFrame.BlazorWebView.csproj
@@ -1,21 +1,20 @@
InfiniLore.InfiniFrame.BlazorWebView
- Library
Integrates a full Blazor WebAssembly-style application into a native InfiniFrame window with no HTTP server required. The Blazor runtime runs entirely in-process.
-
-
-
-
+
-
-
+
+
+
+
+
diff --git a/src/InfiniFrame.BlazorWebView/InfiniFrameApplicationBlazorWebViewExtensions.cs b/src/InfiniFrame.BlazorWebView/InfiniFrameApplicationBlazorWebViewExtensions.cs
new file mode 100644
index 000000000..12a8164c9
--- /dev/null
+++ b/src/InfiniFrame.BlazorWebView/InfiniFrameApplicationBlazorWebViewExtensions.cs
@@ -0,0 +1,257 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+using System.Reflection;
+using InfiniFrame.Application;
+using InfiniFrame.BlazorWebView.FileProviders;
+using InfiniFrame.Security;
+using InfiniFrame.StaticAssets;
+using InfiniFrame.Window;
+using InfiniFrame.Window.Features.WebMessaging.Handlers;
+using Microsoft.AspNetCore.Components;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.FileProviders;
+using Microsoft.Extensions.Options;
+
+namespace InfiniFrame.BlazorWebView;
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+
+public sealed class InfiniFrameBlazorWebViewConfiguration {
+ private readonly IServiceCollection _services;
+ private readonly List> _windowConfigurations = [];
+
+ internal InfiniFrameBlazorWebViewConfiguration(IServiceCollection services) {
+ _services = services;
+ ConfigureServices();
+ }
+
+ public IInfiniFrameRootComponentList RootComponents { get; } = new InfiniFrameRootComponentList();
+
+ public InfiniFrameBlazorWebViewConfiguration Configure(Action configure) {
+ ArgumentNullException.ThrowIfNull(configure);
+ _services.Configure(configure);
+ return this;
+ }
+
+ public InfiniFrameBlazorWebViewConfiguration ConfigureWindow(Action configure) {
+ ArgumentNullException.ThrowIfNull(configure);
+ _windowConfigurations.Add(configure);
+ return this;
+ }
+
+ internal void AddSingleFileProvider(IFileProvider provider) {
+ ArgumentNullException.ThrowIfNull(provider);
+ _services.AddSingleton(provider);
+ }
+
+ internal void Apply(InfiniFrameApplication application, IReadOnlyList windowIds) {
+ IServiceProvider services = application.RootServiceProvider;
+ var manager = services.GetRequiredService();
+ InfiniFrameBlazorAppConfiguration appConfig = services.GetService>()?.Value
+ ?? new InfiniFrameBlazorAppConfiguration();
+
+ var jsConfiguration =
+ services.GetService();
+ if (jsConfiguration is not null) {
+ application.WindowCreated += _ => {
+ foreach ((Type componentType, string selector) in RootComponents) {
+ jsConfiguration.Add(componentType, selector);
+ }
+ };
+ }
+
+ IDisposable? exceptionRegistration = TryRegisterUnhandledExceptionHandler(services);
+ application.ApplyWindowIntegrationToAll(windowBuilder => {
+ if (!windowBuilder.EventsStore.CustomScheme.ContainsKey(InfiniFrameWebViewManager.BlazorAppScheme))
+ windowBuilder.RegisterCustomSchemeHandler(InfiniFrameWebViewManager.BlazorAppScheme, manager.HandleWebRequest);
+ });
+ Task? managerDisposal = null;
+ application.ApplyWindowIntegration(windowIds, "BlazorWebView", configure: windowBuilder => {
+ foreach (Action configure in _windowConfigurations) {
+ configure(windowBuilder);
+ }
+
+ InfiniFrameUriSecurityPolicyRegistry.ConfigureForBuilder(
+ windowBuilder,
+ configure: policyBuilder => policyBuilder.AddTrustedOrigin(appConfig.AppBaseUri));
+ windowBuilder.StaticAssets = services.GetRequiredService().DeepCopy();
+ windowBuilder.RegisterWebMessageReceivedHandler(manager.HandleWebMessage);
+ windowBuilder.RegisterGetWebMessageHandler();
+
+ windowBuilder.RegisterWindowClosedHandler(_ => {
+ managerDisposal ??= DisposeManagerAsync();
+
+ async Task DisposeManagerAsync() {
+ if (manager is not IAsyncDisposable asyncManager)
+ throw new InvalidOperationException("The Blazor WebView manager is not asynchronously disposable.");
+
+ await Task.Run(async () => await asyncManager.DisposeAsync().ConfigureAwait(false))
+ .ConfigureAwait(false);
+ }
+ });
+ windowBuilder.SetStartPageUrl(BuildStartupUrl(appConfig));
+ });
+ if (exceptionRegistration is not null)
+ application.RegisterShutdownAction(() => {
+ exceptionRegistration.Dispose();
+ return Task.CompletedTask;
+ });
+ }
+
+ private void ConfigureServices() {
+ IFileProvider fileProvider = ConfigureFileProvider(null);
+ _services.TryAddSingleton();
+ _services.AddOptions();
+ _services
+ .AddInfiniFrame()
+ .AddScoped(sp => {
+ var handler = sp.GetRequiredService();
+ Uri appBaseUri = sp.GetRequiredService>().Value.AppBaseUri;
+ return new HttpClient(handler) { BaseAddress = appBaseUri };
+ })
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddBlazorWebView()
+ .AddSingleton(fileProvider)
+ .AddSingleton(static provider => {
+ InfiniFrameBlazorAppConfiguration config = provider.GetService>()?.Value
+ ?? new InfiniFrameBlazorAppConfiguration();
+ return new InfiniFrameStaticAssets {
+ FileProvider = provider.GetRequiredService(),
+ BaseUri = config.AppBaseUri.ToString(),
+ DefaultDocument = NormalizeHostPage(config.HostPage)
+ };
+ })
+ .AddSingleton(RootComponents)
+ .AddSingleton(RootComponents.JSComponents);
+
+ _services.TryAddSingleton();
+ _services.AddInfiniFrameJs();
+ }
+
+ private static IFileProvider ConfigureFileProvider(IFileProvider? fileProvider) {
+ if (fileProvider is not null) return fileProvider;
+
+ string baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
+ var providers = new List();
+ IFileProvider? staticWebAssets = StaticWebAssetsRuntimeFileProvider.TryCreate(baseDirectory, Assembly.GetEntryAssembly());
+
+ if (staticWebAssets is not null) providers.Add(staticWebAssets);
+ string wwwroot = Path.Join(baseDirectory, "wwwroot");
+ PhysicalFileProvider? physical = Directory.Exists(wwwroot) ? new PhysicalFileProvider(wwwroot) : null;
+
+ if (physical is not null) providers.Add(physical);
+ return providers.Count switch {
+ 0 => new NullFileProvider(),
+ 1 => providers[0],
+ _ => new DisposableCompositeFileProvider(providers, physical!)
+ };
+ }
+
+ private static string BuildStartupUrl(InfiniFrameBlazorAppConfiguration configuration) {
+ Uri appBaseUri = configuration.AppBaseUri;
+ string hostPage = NormalizeHostPage(configuration.HostPage);
+
+ return string.Equals(hostPage, "index.html", StringComparison.OrdinalIgnoreCase)
+ ? appBaseUri.ToString()
+ : new Uri(appBaseUri, hostPage).ToString();
+ }
+
+ private static string NormalizeHostPage(string? hostPage) =>
+ !string.IsNullOrWhiteSpace(hostPage) ? hostPage.TrimStart('/') : "index.html";
+
+ private static IDisposable? TryRegisterUnhandledExceptionHandler(IServiceProvider services) {
+ bool enabled = services.GetService>()?.Value
+ .EnableGlobalUnhandledExceptionHandler ?? true;
+ if (!enabled) return null;
+
+ var source = services.GetRequiredService();
+ return source.Register((_, error) => {
+ try {
+ var window = services.GetService();
+ window?.Invoke(() => window.ShowMessage("Fatal exception", error.ExceptionObject.ToString()));
+ }
+ catch (ObjectDisposedException) {}
+ catch (InvalidOperationException) {}
+ });
+ }
+}
+
+public static class InfiniFrameApplicationBlazorWebViewExtensions {
+ public static InfiniFrameApplicationBuilder WithBlazorWebView(
+ this InfiniFrameApplicationBuilder builder,
+ Action configure
+ ) => builder.UseBlazorWebView(configure);
+
+ public static InfiniFrameApplicationBuilder WithBlazorWebView(
+ this InfiniFrameApplicationBuilder builder,
+ string windowId,
+ Action configure
+ ) => builder.UseBlazorWebView(windowId, configure);
+
+ public static InfiniFrameApplicationBuilder WithBlazorWebView(
+ this InfiniFrameApplicationBuilder builder,
+ IEnumerable windowIds,
+ Action configure
+ ) => builder.UseBlazorWebView(windowIds, configure);
+
+ public static InfiniFrameApplicationBuilder UseBlazorWebView(
+ this InfiniFrameApplicationBuilder builder,
+ Action configure
+ ) => builder.UseBlazorWebView(configure, []);
+
+ public static InfiniFrameApplicationBuilder UseBlazorWebView(
+ this InfiniFrameApplicationBuilder builder,
+ string windowId,
+ Action configure
+ ) => builder.UseBlazorWebView(configure, windowId);
+
+ public static InfiniFrameApplicationBuilder UseBlazorWebView(
+ this InfiniFrameApplicationBuilder builder,
+ IEnumerable windowIds,
+ Action configure
+ ) {
+ ArgumentNullException.ThrowIfNull(windowIds);
+ return builder.UseBlazorWebView(configure, windowIds.ToArray());
+ }
+
+ public static InfiniFrameApplicationBuilder UseBlazorWebView(
+ this InfiniFrameApplicationBuilder builder,
+ Action configure,
+ params string[] windowIds
+ ) {
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(configure);
+ ArgumentNullException.ThrowIfNull(windowIds);
+ if (windowIds.Length > 1)
+ throw new NotSupportedException("BlazorWebView can currently target only one window.");
+ if (builder.Services.Any(static sd => sd.ServiceType == typeof(BlazorWebViewMarker)))
+ throw new InvalidOperationException(
+ "UseBlazorWebView has already been registered on this application builder. " +
+ "Multiple Blazor WebView integrations are not supported. " +
+ "Register a single Blazor WebView and target it at the desired window.");
+
+ var configuration = new InfiniFrameBlazorWebViewConfiguration(builder.Services);
+ configure(configuration);
+ builder.Services.AddSingleton(provider =>
+ ResolveTargetWindow(provider.GetRequiredService(), windowIds));
+ builder.AddIntegration(application => {
+ application.ValidateWindowIntegrationTargets(windowIds, "BlazorWebView");
+ configuration.Apply(application, windowIds);
+ });
+ return builder;
+ }
+
+ private static IInfiniFrameWindow ResolveTargetWindow(IInfiniFrameApplication application, IReadOnlyList windowIds) {
+ if (windowIds.Count > 0) return application.GetWindow(windowIds[0]);
+ if (application.Windows.Count == 1) return application.Windows[0];
+
+ throw new InvalidOperationException("BlazorWebView could not resolve its target window.");
+ }
+}
diff --git a/src/InfiniFrame.BlazorWebView/InfiniFrameBlazorApp.cs b/src/InfiniFrame.BlazorWebView/InfiniFrameBlazorApp.cs
deleted file mode 100644
index 2b64655ec..000000000
--- a/src/InfiniFrame.BlazorWebView/InfiniFrameBlazorApp.cs
+++ /dev/null
@@ -1,117 +0,0 @@
-// ---------------------------------------------------------------------------------------------------------------------
-// Imports
-// ---------------------------------------------------------------------------------------------------------------------
-using InfiniFrame.Utilities;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-
-namespace InfiniFrame.BlazorWebView;
-// ---------------------------------------------------------------------------------------------------------------------
-// Code
-// ---------------------------------------------------------------------------------------------------------------------
-///
-/// Represents a running Blazor application hosted in an InfiniFrame native window.
-/// Manages the application lifecycle and service provider.
-///
-public class InfiniFrameBlazorApp(
- IServiceProvider provider,
- IInfiniFrameRootComponentList rootComponents,
- IInfiniFrameJsComponentConfiguration? rootComponentConfiguration = null,
- IDisposable? unhandledExceptionRegistration = null
-) : IInfiniFrameBlazorApp {
-
- private int _disposed;
- ///
- /// Gets the service provider for the running Blazor application.
- ///
- public IServiceProvider ServiceProvider { get; } = provider;
- private IInfiniFrameRootComponentList RootComponents { get; } = rootComponents;
- private IInfiniFrameJsComponentConfiguration? RootComponentConfiguration { get; } = rootComponentConfiguration;
- private IDisposable? UnhandledExceptionRegistration { get; } = unhandledExceptionRegistration;
-
- // -----------------------------------------------------------------------------------------------------------------
- // Methods
- // -----------------------------------------------------------------------------------------------------------------
- ///
- public async Task RunAsync(CancellationToken ct = default) {
- ObjectDisposedException.ThrowIf(Volatile.Read(ref _disposed) != 0, this);
-
- var window = ServiceProvider.GetRequiredService();
-
- RegisterRootComponents();
-
- try {
- await window.WaitForCloseAsync(ct).ConfigureAwait(false);
- }
- finally {
- await DisposeAsync().ConfigureAwait(false);
- }
- }
-
- ///
- ///
- /// This method uses synchronous-over-async patterns for disposal. It should only be called
- /// from threads without a SynchronizationContext. Prefer for async contexts.
- ///
- public void Run() {
- ObjectDisposedException.ThrowIf(Volatile.Read(ref _disposed) != 0, this);
-
- if (SynchronizationContext.Current is not null) {
- throw new InvalidOperationException(
- "Run() must be called from a thread without a SynchronizationContext to avoid deadlock during disposal. " +
- "Use RunAsync() instead.");
- }
-
- var window = ServiceProvider.GetRequiredService();
-
- RegisterRootComponents();
-
- try {
- window.WaitForClose();
- }
- finally {
- DisposeAsync().AsTask().GetAwaiter().GetResult();
- }
- }
-
- ///
- /// Asynchronously disposes of the application and its service provider.
- ///
- ///
- /// This method uses best-effort disposal: exceptions thrown during service provider
- /// disposal are caught and logged but do not propagate to the caller. This prevents
- /// resource cleanup failures from masking the original application shutdown.
- ///
- public async ValueTask DisposeAsync() {
- if (Interlocked.Exchange(ref _disposed, 1) != 0) return;
-
- ILogger? logger = null;
-
- try {
- logger = ServiceProvider.GetService>();
-
- UnhandledExceptionRegistration?.Dispose();
-
- switch (ServiceProvider) {
- case IAsyncDisposable asyncDisposable:
- await asyncDisposable.DisposeAsync().ConfigureAwait(false);
- break;
-
- case IDisposable disposable:
- disposable.Dispose();
- break;
- }
- }
- catch (Exception e) when (ExceptionsUtility.IsNonFatalException(e)) {
- logger?.LogError(e, "Error disposing of InfiniFrameBlazorApp");
- }
- }
-
- private void RegisterRootComponents() {
- if (RootComponentConfiguration is null) return;
-
- foreach ((Type, string) component in RootComponents) {
- RootComponentConfiguration.Add(component.Item1, component.Item2);
- }
- }
-}
diff --git a/src/InfiniFrame.BlazorWebView/InfiniFrameBlazorAppBuilder.cs b/src/InfiniFrame.BlazorWebView/InfiniFrameBlazorAppBuilder.cs
deleted file mode 100644
index b892c038f..000000000
--- a/src/InfiniFrame.BlazorWebView/InfiniFrameBlazorAppBuilder.cs
+++ /dev/null
@@ -1,241 +0,0 @@
-// ---------------------------------------------------------------------------------------------------------------------
-// Imports
-// ---------------------------------------------------------------------------------------------------------------------
-using System.Reflection;
-using InfiniFrame.BlazorWebView.FileProviders;
-using InfiniFrame.Security;
-using InfiniFrame.StaticAssets;
-using Microsoft.AspNetCore.Components;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.DependencyInjection.Extensions;
-using Microsoft.Extensions.FileProviders;
-using Microsoft.Extensions.Options;
-
-namespace InfiniFrame.BlazorWebView;
-// ---------------------------------------------------------------------------------------------------------------------
-// Code
-// ---------------------------------------------------------------------------------------------------------------------
-///
-/// Builder for creating a Blazor application hosted in an InfiniFrame native window.
-///
-public class InfiniFrameBlazorAppBuilder : IInfiniFrameBlazorAppBuilder {
-
- // -----------------------------------------------------------------------------------------------------------------
- // Constructors
- // -----------------------------------------------------------------------------------------------------------------
- private InfiniFrameBlazorAppBuilder() {}
- ///
- public IInfiniFrameRootComponentList RootComponents { get; } = new InfiniFrameRootComponentList();
- ///
- public IServiceCollection Services { get; } = new ServiceCollection();
- ///
- public IInfiniFrameWindowBuilder WindowBuilder { get; } = InfiniFrameWindowBuilder.Create();
-
- ///
- /// Creates a default builder with standard configuration, command-line args, and window builder action.
- ///
- /// Optional command-line arguments.
- /// An optional action to configure the window builder.
- /// A new instance.
- public static InfiniFrameBlazorAppBuilder CreateDefault(
- string[]? args = null,
- Action? windowBuilder = null
- )
- => CreateDefault(null, args, windowBuilder);
-
- ///
- /// Creates a default builder with standard configuration and command-line args.
- ///
- /// An optional file provider for static assets.
- /// Optional command-line arguments.
- /// An optional action to configure the window builder.
- /// A new instance.
- public static InfiniFrameBlazorAppBuilder CreateDefault(IFileProvider? fileProvider, string[]? args = null, Action? windowBuilder = null) {
- // We don't use the args for anything right now, but we want to accept them
- // here so that it shows up this way in the project templates.
- var appBuilder = new InfiniFrameBlazorAppBuilder();
- IFileProvider resolvedFileProvider = ConfigureFileProvider(fileProvider);
-
- appBuilder.Services.AddOptions();
-
- appBuilder.Services
- .AddInfiniFrame()
- .AddScoped(static sp => {
- var handler = sp.GetRequiredService();
- return new HttpClient(handler) { BaseAddress = new Uri(InfiniFrameWebViewManager.AppBaseUri) };
- })
- .AddSingleton()
- .AddSingleton()
- .AddSingleton()
- .AddSingleton()
- .AddSingleton()
- .AddSingleton()
- .AddSingleton(static provider => provider.GetRequiredService().Build(provider))
- .AddBlazorWebView()
- .AddSingleton(resolvedFileProvider)
- .AddSingleton(static provider => {
- InfiniFrameBlazorAppConfiguration config = provider.GetService>()?.Value
- ?? new InfiniFrameBlazorAppConfiguration();
-
- return new InfiniFrameStaticAssets {
- FileProvider = provider.GetRequiredService(),
- BaseUri = config.AppBaseUri.ToString(),
- DefaultDocument = NormalizeHostPage(config.HostPage)
- };
- })
- .AddSingleton(appBuilder.WindowBuilder)
- .AddSingleton(appBuilder.RootComponents)
- .AddSingleton(appBuilder.RootComponents.JSComponents);
-
- appBuilder.Services.TryAddSingleton();
-
- appBuilder.Services.AddInfiniFrameJs();
- appBuilder.WindowBuilder.RegisterGetWebMessageHandler();
-
- windowBuilder?.Invoke(appBuilder.WindowBuilder);
-
- return appBuilder;
- }
-
- ///
- /// Configures the file provider to be used by the application.
- /// If a custom is provided, that instance will be used.
- /// Otherwise, a default provider will be configured based on the application's "wwwroot" directory.
- ///
- ///
- /// An optional instance.
- ///
- ///
- /// An instance of that represents either the specified file provider
- /// or the default provider if none is supplied.
- ///
- private static IFileProvider ConfigureFileProvider(IFileProvider? fileProvider) {
- if (fileProvider is not null) return fileProvider;
-
- string baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
-
- var providers = new List();
-
- IFileProvider? staticWebAssetsProvider = StaticWebAssetsRuntimeFileProvider.TryCreate(baseDirectory, Assembly.GetEntryAssembly());
- if (staticWebAssetsProvider is not null) providers.Add(staticWebAssetsProvider);
-
- string defaultWwwrootPath = Path.Join(baseDirectory, "wwwroot");
- bool hasPhysicalWwwroot = Directory.Exists(defaultWwwrootPath);
- PhysicalFileProvider? physicalWwwrootProvider = hasPhysicalWwwroot
- ? new PhysicalFileProvider(defaultWwwrootPath)
- : null;
- if (physicalWwwrootProvider is not null) providers.Add(physicalWwwrootProvider);
-
- return providers.Count switch {
- 0 => new NullFileProvider(),
- 1 => providers[0],
- _ => new DisposableCompositeFileProvider(providers, physicalWwwrootProvider!)
- };
-
- }
-
- ///
- /// Configures the InfiniFrame window builder action.
- ///
- /// The action to configure the window builder.
- /// The for chaining.
- public InfiniFrameBlazorAppBuilder WithInfiniFrameWindowBuilder(Action windowBuilder) {
- windowBuilder.Invoke(WindowBuilder);
- return this;
- }
-
- ///
- /// Builds a new using a service provider created from .
- ///
- /// A newly created .
- public InfiniFrameBlazorApp Build()
- => Build(Services.BuildServiceProvider());
-
- ///
- /// Builds a new using an externally supplied .
- ///
- ///
- /// The pre-built service provider to use for resolving all application services.
- /// Ownership is transferred to the returned app instance; when that app is disposed, this provider is disposed if it
- /// implements
- /// or . Do not dispose the same provider separately.
- ///
- /// A newly created .
- ///
- /// Calling this method more than once on the same builder instance is not supported. Each call mutates builder state
- /// (for example, by registering additional scheme handlers), which can lead to duplicate registrations.
- /// Create a new builder for each app instance.
- ///
- public InfiniFrameBlazorApp Build(IServiceProvider serviceProvider) {
- ArgumentNullException.ThrowIfNull(serviceProvider);
-
- var manager = serviceProvider.GetRequiredService();
- InfiniFrameBlazorAppConfiguration appConfig = serviceProvider.GetService>()?.Value
- ?? new InfiniFrameBlazorAppConfiguration();
- InfiniFrameUriSecurityPolicyRegistry.ConfigureForBuilder(
- WindowBuilder,
- configure: policyBuilder => policyBuilder.AddTrustedOrigin(appConfig.AppBaseUri));
- string startupUrl = BuildStartupUrl(appConfig);
- var staticAssets = serviceProvider.GetRequiredService();
-
- WindowBuilder.StaticAssets = staticAssets.DeepCopy();
-
- if (!WindowBuilder.EventsStore.CustomScheme.ContainsKey(InfiniFrameWebViewManager.BlazorAppScheme)) {
- WindowBuilder.RegisterCustomSchemeHandler(InfiniFrameWebViewManager.BlazorAppScheme, manager.HandleWebRequest);
- }
-
- WindowBuilder.SetStartPageUrl(startupUrl);
-
- IDisposable? unhandledExceptionRegistration = TryRegisterUnhandledExceptionHandler(serviceProvider);
-
- return new InfiniFrameBlazorApp(
- serviceProvider,
- serviceProvider.GetRequiredService(),
- serviceProvider.GetService(),
- unhandledExceptionRegistration
- );
- }
-
- private static string BuildStartupUrl(InfiniFrameBlazorAppConfiguration configuration) {
- Uri appBaseUri = configuration.AppBaseUri;
- string hostPage = NormalizeHostPage(configuration.HostPage);
-
- return string.Equals(hostPage, "index.html", StringComparison.OrdinalIgnoreCase)
- ? appBaseUri.ToString()
- : new Uri(appBaseUri, hostPage).ToString();
- }
-
- private static string NormalizeHostPage(string? hostPage)
- => !string.IsNullOrWhiteSpace(hostPage)
- ? hostPage.TrimStart('/')
- : "index.html";
-
- private static IDisposable? TryRegisterUnhandledExceptionHandler(IServiceProvider serviceProvider) {
- bool enableGlobalUnhandledExceptionHandler = serviceProvider.GetService>()?
- .Value.EnableGlobalUnhandledExceptionHandler ?? true;
-
- if (!enableGlobalUnhandledExceptionHandler) return null;
-
- var exceptionSource = serviceProvider.GetRequiredService();
-
- return exceptionSource.Register((_, error) => {
- try {
- var window = serviceProvider.GetService();
-
- // Only interact if safe
- window?.Invoke(() => {
- window.ShowMessage(
- "Fatal exception",
- error.ExceptionObject.ToString()
- );
- });
- }
- catch (ObjectDisposedException) {
- // Window already closed; nothing to report.
- }
- catch (InvalidOperationException) {
- // Service not available; nothing to report.
- }
- });
- }
-}
diff --git a/src/InfiniFrame.BlazorWebView/InfiniFrameBlazorAppConfiguration.cs b/src/InfiniFrame.BlazorWebView/InfiniFrameBlazorAppConfiguration.cs
index 23923fe1c..8447ec080 100644
--- a/src/InfiniFrame.BlazorWebView/InfiniFrameBlazorAppConfiguration.cs
+++ b/src/InfiniFrame.BlazorWebView/InfiniFrameBlazorAppConfiguration.cs
@@ -34,8 +34,6 @@ public class InfiniFrameBlazorAppConfiguration {
///
/// Gets or sets how outbound messages are handled when is reached.
/// The default rejects the new message, which provides immediate backpressure to the non-awaitable Blazor API.
- /// Note: The current implementation always uses TryWrite (non-blocking), so this setting only controls
- /// diagnostic logging and is reserved for future use with blocking write paths.
///
public BoundedChannelFullMode WebMessageQueueFullMode { get; set; } = BoundedChannelFullMode.DropWrite;
}
diff --git a/src/InfiniFrame.BlazorWebView/InfiniFrameJsComponentConfiguration.cs b/src/InfiniFrame.BlazorWebView/InfiniFrameJsComponentConfiguration.cs
index 788e841bf..d2091886b 100644
--- a/src/InfiniFrame.BlazorWebView/InfiniFrameJsComponentConfiguration.cs
+++ b/src/InfiniFrame.BlazorWebView/InfiniFrameJsComponentConfiguration.cs
@@ -38,7 +38,7 @@ public void Add(Type typeComponent, string selector, IDictionary manager.AddRootComponentAsync(typeComponent, selector, parameterView));
addComponentTask.ContinueWith(
continuationAction: task => {
- logger.LogError(task.Exception, "Failed to add root component '{ComponentType}' for selector '{Selector}'.", typeComponent, selector);
+ logger.LogError(task.Exception, "Failed to add root component '{ComponentType}' for selector '{Selector}'", typeComponent, selector);
Interlocked.Exchange(ref _lastAddComponentException, task.Exception);
},
CancellationToken.None,
diff --git a/src/InfiniFrame.BlazorWebView/InfiniFrameWebViewManager.cs b/src/InfiniFrame.BlazorWebView/InfiniFrameWebViewManager.cs
index 7dd3033ba..9dc63bb54 100644
--- a/src/InfiniFrame.BlazorWebView/InfiniFrameWebViewManager.cs
+++ b/src/InfiniFrame.BlazorWebView/InfiniFrameWebViewManager.cs
@@ -40,7 +40,8 @@ public class InfiniFrameWebViewManager : WebViewManager, IInfiniFrameWebViewMana
private readonly Task _messagePumpTask;
private readonly int _messageQueueCapacity;
private readonly BoundedChannelFullMode _messageQueueFullMode;
- private readonly IInfiniFrameUriSecurityPolicy _uriSecurityPolicy;
+ private readonly Uri _appBaseUri;
+ private readonly IInfiniFrameUriSecurityPolicy _fallbackUriSecurityPolicy;
private int _disposeStarted;
private int _disposed;
@@ -50,15 +51,13 @@ public class InfiniFrameWebViewManager : WebViewManager, IInfiniFrameWebViewMana
///
/// Initializes a new instance of the class.
///
- /// The window builder for configuring the native window.
/// The service provider for dependency injection.
- /// The Blazor dispatcher for thread marshalling.
+ /// The Blazor dispatcher for thread marshaling.
/// The file provider for serving static assets.
/// The JavaScript component configuration store.
/// The Blazor application configuration.
/// The logger
public InfiniFrameWebViewManager(
- IInfiniFrameWindowBuilder builder,
IServiceProvider provider,
Dispatcher dispatcher,
IFileProvider fileProvider,
@@ -69,6 +68,19 @@ ILogger logger
: base(provider, dispatcher, config.Value.AppBaseUri, fileProvider, jsComponents, config.Value.HostPage) {
_logger = logger;
InfiniFrameBlazorAppConfiguration configuration = config.Value;
+ if (configuration.AppBaseUri is null
+ || !configuration.AppBaseUri.IsAbsoluteUri
+ || string.IsNullOrWhiteSpace(configuration.AppBaseUri.Scheme)
+ || string.IsNullOrWhiteSpace(configuration.AppBaseUri.Host)) {
+ throw new ArgumentException("AppBaseUri must be an absolute URI with a scheme and host.", nameof(configuration.AppBaseUri));
+ }
+
+ if (!string.Equals(configuration.AppBaseUri.Scheme, BlazorAppScheme, StringComparison.OrdinalIgnoreCase)) {
+ throw new ArgumentException(
+ $"AppBaseUri must use the '{BlazorAppScheme}' scheme. Got '{configuration.AppBaseUri.Scheme}'.",
+ nameof(configuration.AppBaseUri));
+ }
+ _appBaseUri = configuration.AppBaseUri;
if (configuration.WebMessageQueueCapacity <= 0) {
throw new ArgumentOutOfRangeException(
nameof(configuration.WebMessageQueueCapacity),
@@ -76,34 +88,25 @@ ILogger logger
"The WebView message queue capacity must be positive.");
}
+ // TryWrite cannot report which item DropWrite discarded. Use Wait so the non-awaitable
+ // producer receives a false result and the loss is observable through diagnostics.
+ BoundedChannelFullMode effectiveFullMode = configuration.WebMessageQueueFullMode == BoundedChannelFullMode.DropWrite
+ ? BoundedChannelFullMode.Wait
+ : configuration.WebMessageQueueFullMode;
_channel = Channel.CreateBounded(new BoundedChannelOptions(configuration.WebMessageQueueCapacity) {
SingleReader = true,
SingleWriter = false,
- FullMode = configuration.WebMessageQueueFullMode,
+ FullMode = effectiveFullMode,
AllowSynchronousContinuations = false
});
_messageQueueCapacity = configuration.WebMessageQueueCapacity;
- _messageQueueFullMode = configuration.WebMessageQueueFullMode;
- _uriSecurityPolicy = InfiniFrameUriSecurityPolicyRegistry
- .GetForBuilder(builder)
+ _messageQueueFullMode = effectiveFullMode;
+ _fallbackUriSecurityPolicy = InfiniFrameUriSecurityPolicy.Default
.WithTrustedOrigin(configuration.AppBaseUri);
// ReSharper disable once ConvertClosureToMethodGroup
LazyWindow = new Lazy(() => provider.GetRequiredService());
- builder.RegisterWebMessageReceivedHandler((_, message, origin) => {
- if (IsDisposingOrDisposed) return;
-
- _logger.LogTrace("Web message callback received from native. Origin: {Origin}, Length: {Length}", origin, message.Length);
-
- try {
- HandleWebMessage((message, origin));
- }
- catch (Exception ex) when (ExceptionsUtility.IsNonFatalException(ex)) {
- _logger.LogWarning(ex, "Unhandled exception while handling native web message callback.");
- }
- });
-
_messagePumpTask = MessagePump();
_logger.LogDebug(
"Started WebView message pump. QueueCapacity: {QueueCapacity}, FullMode: {FullMode}",
@@ -135,7 +138,8 @@ ILogger logger
return default;
}
- if (!_uriSecurityPolicy.IsNavigationSchemeAllowed(requestUri.Scheme)) {
+ IInfiniFrameUriSecurityPolicy uriSecurityPolicy = GetUriSecurityPolicy(infiniFrameWindow);
+ if (!uriSecurityPolicy.IsNavigationSchemeAllowed(requestUri.Scheme)) {
_logger.LogWarning(
"Rejected web request due to disallowed URI scheme. Scheme: {Scheme}, Url: {Url}",
requestUri.Scheme,
@@ -143,11 +147,11 @@ ILogger logger
return default;
}
- if (!_uriSecurityPolicy.IsTrustedOrigin(requestUri)) {
+ if (!uriSecurityPolicy.IsTrustedOrigin(requestUri)) {
_logger.LogWarning(
"Rejected web request due to untrusted origin. RequestOrigin: {RequestOrigin}, TrustedOrigins: {TrustedOrigins}",
requestUri,
- _uriSecurityPolicy.TrustedOrigins);
+ uriSecurityPolicy.TrustedOrigins);
return default;
}
@@ -183,37 +187,55 @@ ILogger logger
// -----------------------------------------------------------------------------------------------------------------
// Web message handling
// -----------------------------------------------------------------------------------------------------------------
- private void HandleWebMessage((string Message, string? Origin) state) {
+ ///
+ public void HandleWebMessage(IInfiniFrameWindow window, string message, string? origin) {
+ ArgumentNullException.ThrowIfNull(window);
+ ArgumentNullException.ThrowIfNull(message);
+
+ if (IsDisposingOrDisposed) return;
+
+ _logger.LogTrace("Web message callback received from native. Origin: {Origin}, Length: {Length}", origin, message.Length);
+
+ try {
+ HandleWebMessageCore(window, message, origin);
+ }
+ catch (Exception ex) when (ExceptionsUtility.IsNonFatalException(ex)) {
+ _logger.LogWarning(ex, "Unhandled exception while handling native web message callback");
+ }
+ }
+
+ private void HandleWebMessageCore(IInfiniFrameWindow window, string message, string? origin) {
if (IsDisposingOrDisposed) return;
Uri? messageOriginUrl;
- if (!string.IsNullOrWhiteSpace(state.Origin)) {
- if (!Uri.TryCreate(state.Origin, UriKind.Absolute, out messageOriginUrl)) {
+ if (!string.IsNullOrWhiteSpace(origin)) {
+ if (!Uri.TryCreate(origin, UriKind.Absolute, out messageOriginUrl)) {
_logger.LogWarning(
"Rejected web message because origin parsing failed. Origin: {Origin}",
- state.Origin);
+ origin);
return;
}
}
- else if (Uri.TryCreate(AppBaseUri, UriKind.Absolute, out Uri? fallback)) {
- messageOriginUrl = fallback;
+ else if (_appBaseUri.IsAbsoluteUri) {
+ messageOriginUrl = _appBaseUri;
_logger.LogDebug(
"Web message origin missing. Falling back to AppBaseUri origin: {FallbackOrigin}",
- fallback);
+ _appBaseUri);
}
else {
_logger.LogWarning(
- "Rejected web message because origin is missing or unknown.");
+ "Rejected web message because origin is missing or unknown");
return;
}
- if (!_uriSecurityPolicy.IsTrustedOrigin(messageOriginUrl)) {
+ IInfiniFrameUriSecurityPolicy uriSecurityPolicy = GetUriSecurityPolicy(window);
+ if (!uriSecurityPolicy.IsTrustedOrigin(messageOriginUrl)) {
_logger.LogWarning(
"Rejected web message due to origin mismatch. Origin: {MessageOrigin}, TrustedOrigins: {TrustedOrigins}",
messageOriginUrl,
- _uriSecurityPolicy.TrustedOrigins);
+ uriSecurityPolicy.TrustedOrigins);
return;
}
@@ -221,9 +243,14 @@ private void HandleWebMessage((string Message, string? Origin) state) {
// messages because the pump synchronously invokes that same thread to send responses.
if (IsDisposingOrDisposed) return;
- MessageReceived(messageOriginUrl, state.Message);
+ MessageReceived(messageOriginUrl, message);
}
+ private IInfiniFrameUriSecurityPolicy GetUriSecurityPolicy(IInfiniFrameWindow? window) =>
+ window is null
+ ? _fallbackUriSecurityPolicy
+ : InfiniFrameUriSecurityPolicyRegistry.GetForWindow(window);
+
// -----------------------------------------------------------------------------------------------------------------
// Navigation
// -----------------------------------------------------------------------------------------------------------------
@@ -233,7 +260,7 @@ protected override void NavigateCore(Uri absoluteUri) {
protected override void SendMessage(string message) {
if (IsDisposingOrDisposed || _messagePumpShutdown.IsCancellationRequested) {
- _logger.LogTrace("Discarded outbound WebView message because the manager is shutting down.");
+ _logger.LogTrace("Discarded outbound WebView message because the manager is shutting down");
return;
}
@@ -257,16 +284,16 @@ private async Task MessagePump() {
}
}
catch (ObjectDisposedException ex) {
- _logger.LogDebug(ex, "WebView message pump observed disposed dependencies; stopping.");
+ _logger.LogDebug(ex, "WebView message pump observed disposed dependencies; stopping");
}
catch (ChannelClosedException ex) {
- _logger.LogDebug(ex, "WebView message channel closed; stopping message pump.");
+ _logger.LogDebug(ex, "WebView message channel closed; stopping message pump");
}
catch (OperationCanceledException) {
- _logger.LogDebug("WebView message pump cancellation requested.");
+ _logger.LogDebug("WebView message pump cancellation requested");
}
catch (Exception ex) when (ExceptionsUtility.IsNonFatalException(ex)) {
- _logger.LogError(ex, "Unhandled exception in WebView message pump.");
+ _logger.LogError(ex, "Unhandled exception in WebView message pump");
}
}
@@ -274,7 +301,7 @@ protected override async ValueTask DisposeAsyncCore() {
if (Interlocked.Exchange(ref _disposeStarted, 1) != 0) return;
_channel.Writer.TryComplete();
- _messagePumpShutdown.Cancel();
+ await _messagePumpShutdown.CancelAsync();
try {
// Some tests build and dispose of the app without ever creating a native window.
@@ -282,7 +309,7 @@ protected override async ValueTask DisposeAsyncCore() {
// that resolve IInfiniFrameWindow and initialize native resources during teardown.
// Avoid creating a window while disposing of an app that never ran.
if (LazyWindow.IsValueCreated) {
- await base.DisposeAsyncCore();
+ await base.DisposeAsyncCore().ConfigureAwait(false);
}
}
finally {
@@ -290,12 +317,12 @@ protected override async ValueTask DisposeAsyncCore() {
await _messagePumpTask.ConfigureAwait(false);
}
catch (Exception ex) when (ExceptionsUtility.IsNonFatalException(ex)) {
- _logger.LogWarning(ex, "Message pump faulted during WebView manager shutdown.");
+ _logger.LogWarning(ex, "Message pump faulted during WebView manager shutdown");
}
finally {
_messagePumpShutdown.Dispose();
Volatile.Write(ref _disposed, 1);
- _logger.LogDebug("WebView manager disposal completed after the message pump stopped.");
+ _logger.LogDebug("WebView manager disposal completed after the message pump stopped");
}
}
}
diff --git a/src/InfiniFrame.Js/InfiniFrame.Js.csproj b/src/InfiniFrame.Js/InfiniFrame.Js.csproj
index f114d9cbe..046bede34 100644
--- a/src/InfiniFrame.Js/InfiniFrame.Js.csproj
+++ b/src/InfiniFrame.Js/InfiniFrame.Js.csproj
@@ -2,9 +2,6 @@
InfiniLore.InfiniFrame.Js
- Library
-
- true
JavaScript interop utilities for InfiniFrame Blazor applications. Provides pointer capture helpers and built-in window management message handlers.
$([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)/../..'))
@@ -15,6 +12,10 @@
$(MSBuildProjectDirectory)/obj/frontend-build.stamp
+
+
+
+
{
it("registerBlazorCustomElement skips non-EventCallback params", () => {
window.Blazor = {rootComponents: {add: vi.fn()}};
- window.customElements = {define: vi.fn(), get: vi.fn(() => undefined)} as any;
+ Object.defineProperty(window, "customElements", {
+ configurable: true,
+ value: {define: vi.fn(), get: vi.fn(() => undefined)},
+ writable: true
+ });
initCustomElements(setup);
window.registerBlazorCustomElement!("test-element", [
@@ -390,7 +394,11 @@ describe("customElements", () => {
it("registerBlazorCustomElement skips undefined name params", () => {
window.Blazor = {rootComponents: {add: vi.fn()}};
- window.customElements = {define: vi.fn(), get: vi.fn(() => undefined)} as any;
+ Object.defineProperty(window, "customElements", {
+ configurable: true,
+ value: {define: vi.fn(), get: vi.fn(() => undefined)},
+ writable: true
+ });
initCustomElements(setup);
window.registerBlazorCustomElement!("test-element", [
@@ -403,7 +411,11 @@ describe("customElements", () => {
it("registerBlazorCustomElement skips already-defined elements", () => {
window.Blazor = {rootComponents: {add: vi.fn()}};
- window.customElements = {define: vi.fn(), get: vi.fn(() => ({}))} as any;
+ Object.defineProperty(window, "customElements", {
+ configurable: true,
+ value: {define: vi.fn(), get: vi.fn(() => ({}))},
+ writable: true
+ });
initCustomElements(setup);
window.registerBlazorCustomElement!("existing-element", [{name: "Value", type: "string"}]);
diff --git a/src/InfiniFrame.Js/package-lock.json b/src/InfiniFrame.Js/package-lock.json
index f5a5af422..3b2f27a78 100644
--- a/src/InfiniFrame.Js/package-lock.json
+++ b/src/InfiniFrame.Js/package-lock.json
@@ -7,58 +7,58 @@
"": {
"name": "infinilore.infiniframe.js-build",
"version": "0.1.0",
- "license": "GNUv3",
- "engines": {
- "node": ">=24.15.0"
- },
+ "license": "Apache-2.0",
"devDependencies": {
- "@types/node": "^26.2.0",
- "@vitest/coverage-v8": "^4.1.11",
+ "@types/node": "^26.6.2",
+ "@vitest/coverage-v8": "^5.0.1",
"concurrently": "^10.0.5",
- "jsdom": "^30.0.1",
- "terser": "^5.50.0",
+ "jsdom": "^30.1.0",
+ "terser": "^5.51.2",
"typescript": "^7.0.2",
- "vite": "^8.2.2",
- "vitest": "^4.1.11"
+ "vite": "^8.3.0",
+ "vitest": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=24.15.0"
}
},
"node_modules/@asamuzakjp/css-color": {
- "version": "6.0.5",
- "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-6.0.5.tgz",
- "integrity": "sha512-mbhpPMmnw/kwW19aRNmSUl1QzLbdGo1SCuE49BT98MNwqF6zaHb3o2owssFc/PEO/4t2UjqtCNwocuDtJornzA==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-7.0.1.tgz",
+ "integrity": "sha512-C9duntabagkBZ1LebM7FKmphR4Q1pBclLxVbZETQV0akkFjV0ooFxo8FlvAQyKj9F6l8rEnmidgcTlpyxFYizg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@csstools/css-calc": "^3.2.1",
- "@csstools/css-color-parser": "^4.1.9",
+ "@csstools/css-calc": "^3.4.0",
+ "@csstools/css-color-parser": "^4.2.3",
"@csstools/css-parser-algorithms": "^4.0.0",
- "@csstools/css-tokenizer": "^4.0.0",
- "lru-cache": "^11.5.2"
+ "@csstools/css-tokenizer": "^4.0.1",
+ "lru-cache": "^11.5.3"
},
"engines": {
- "node": "^22.13.0 || >=24.0.0"
+ "node": "^22.22.2 || ^24.15.0 || >=26.0.0"
}
},
"node_modules/@asamuzakjp/dom-selector": {
- "version": "8.3.0",
- "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-8.3.0.tgz",
- "integrity": "sha512-UJLfKXBhrc8i1vH2eJXuYQMwlsLKWFw3O+CPqXSuVEiikeAim3UgrfWX0k4tA/X8cRFM8iZ7OaqBokFGbYusdg==",
+ "version": "9.2.1",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-9.2.1.tgz",
+ "integrity": "sha512-NT4s3yZLjovPpliRpTvdsdzyPjqRqiCZj9MxnarBihbaY5MbAG7DWAJcrLlhmC7xKTNCXsTELcqB8YsqpLnUFQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "bidi-js": "^1.0.3",
+ "bidi-js": "^1.1.0",
"css-tree": "^3.2.1",
"is-potential-custom-element-name": "^1.0.1",
- "lru-cache": "^11.5.2"
+ "lru-cache": "^11.5.3"
},
"engines": {
- "node": "^22.13.0 || >=24.0.0"
+ "node": "^22.22.2 || ^24.15.0 || >=26.0.0"
}
},
"node_modules/@babel/helper-string-parser": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
- "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
+ "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -66,9 +66,9 @@
}
},
"node_modules/@babel/helper-validator-identifier": {
- "version": "7.28.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
- "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
+ "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -76,13 +76,13 @@
}
},
"node_modules/@babel/parser": {
- "version": "7.29.2",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz",
- "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==",
+ "version": "7.29.9",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz",
+ "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.29.0"
+ "@babel/types": "^7.29.8"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -92,14 +92,14 @@
}
},
"node_modules/@babel/types": {
- "version": "7.29.0",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
- "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz",
+ "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-string-parser": "^7.27.1",
- "@babel/helper-validator-identifier": "^7.28.5"
+ "@babel/helper-string-parser": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7"
},
"engines": {
"node": ">=6.9.0"
@@ -129,9 +129,9 @@
}
},
"node_modules/@csstools/color-helpers": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.0.tgz",
- "integrity": "sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==",
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.1.tgz",
+ "integrity": "sha512-gLNsunvwf3mCi5u5o46/Z/JcJMnhbHSaZ69rkgPzNM3J4s8hWwpPUQB6/tt0EDFyCiWzxANlx+2LJwpYj4zS1w==",
"dev": true,
"funding": [
{
@@ -149,9 +149,9 @@
}
},
"node_modules/@csstools/css-calc": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.3.0.tgz",
- "integrity": "sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==",
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.4.0.tgz",
+ "integrity": "sha512-XQKj5B7QiZcHiegCOCAzcAOJdhGgWOHbbu62h5e5mkHnn8lWcfiJhllkqWmxu5zWR9jucPHuo1iTB56P033hcg==",
"dev": true,
"funding": [
{
@@ -173,9 +173,9 @@
}
},
"node_modules/@csstools/css-color-parser": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.10.tgz",
- "integrity": "sha512-UZhQLIUyJaaMepqehrCODwCg2KW25vFvLWBmqYFaPclYvvxzj/sG8LBOhBFCp11i9uE7t1EyS+RAoV9tztPFyw==",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.2.3.tgz",
+ "integrity": "sha512-y4LpL+lmpuyKDiEFq2PnZUVFdAjsoB/qQJod79yLNokXyW7jewi+/WJ69EfItj8A2unWtxXnGjw6LYXgXu5ZjA==",
"dev": true,
"funding": [
{
@@ -189,8 +189,8 @@
],
"license": "MIT",
"dependencies": {
- "@csstools/color-helpers": "^6.1.0",
- "@csstools/css-calc": "^3.3.0"
+ "@csstools/color-helpers": "^6.1.1",
+ "@csstools/css-calc": "^3.4.0"
},
"engines": {
"node": ">=20.19.0"
@@ -224,9 +224,9 @@
}
},
"node_modules/@csstools/css-syntax-patches-for-csstree": {
- "version": "1.1.7",
- "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.7.tgz",
- "integrity": "sha512-fQ+05118eQS1cofO3aJpB5efgpBZMvIzwr/sbC8kDLVA5XLG8q1kJV5yzrUAI1f7lvhPnm8fgIjzFB8/O/5Dig==",
+ "version": "1.1.14",
+ "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.14.tgz",
+ "integrity": "sha512-HpbVXyrofRXpHpgkNIjU/3EWR4WJvOkO3emNK/L6X/mTJU7bGUI3AkkpoTNXznQLp0KRjLHELTGeKI5dIkI9JQ==",
"dev": true,
"funding": [
{
@@ -249,9 +249,9 @@
}
},
"node_modules/@csstools/css-tokenizer": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz",
- "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==",
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.1.tgz",
+ "integrity": "sha512-bPlN9S9O1A0euCpEWE4qnvB5YDuyYVsUTrxSgmAM1Is0j4tICHoVyOVAXfWMP/kS9ZrjvyIXWV2PmomiAXXqOw==",
"dev": true,
"funding": [
{
@@ -269,9 +269,9 @@
}
},
"node_modules/@exodus/bytes": {
- "version": "1.15.1",
- "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz",
- "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==",
+ "version": "1.15.2",
+ "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.2.tgz",
+ "integrity": "sha512-ctkytT+9TXtv6uiVj6Y7IjKh1+PdBGAD34xd0PpQ/qF+R7O/hoEQbZmN89OaCEaZn5I1A9+ZoPxD03unuFhyhQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -319,9 +319,9 @@
}
},
"node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
- "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz",
+ "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==",
"dev": true,
"license": "MIT"
},
@@ -337,19 +337,19 @@
}
},
"node_modules/@oxc-project/types": {
- "version": "0.146.0",
- "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.146.0.tgz",
- "integrity": "sha512-XC0QsnnhVe7sLIWmYmdPw7x5P0h4W8vUU3Nv1ySgWXtvCz8NizoAEpGXA0sOYoJQV2Rl13LgURAHQ5cI5ILCSA==",
+ "version": "0.150.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.150.0.tgz",
+ "integrity": "sha512-rDS5/31E9HfPl/CIzGrn0DOlvBbXFseQ5URJ9sYMfstbKLD/c6Gm9vmRzRGDdAXyOIL4zmO37lc9RIwYqVruZw==",
"dev": true,
"license": "MIT",
"funding": {
- "url": "https://github.com/sponsors/Boshen"
+ "url": "https://github.com/sponsors/oxc-project"
}
},
"node_modules/@rolldown/binding-android-arm-eabi": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.5.tgz",
- "integrity": "sha512-DLe/i+l8ynIBY7XEQ191TeZvCoowIGa18R+dIV30GW7DiOtp74i/xX8hs8GUjW5ARV7VZuie3d6AumSmCwbeRA==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.9.tgz",
+ "integrity": "sha512-tNISae1QEf/vkb3xkRcjV5SEdzPE97We5IVaa2Z8jSszQPZ8U60B/YCYpw4QI7VidYsBtKavczXf+DyDs9WGxw==",
"cpu": [
"arm"
],
@@ -364,9 +364,9 @@
}
},
"node_modules/@rolldown/binding-android-arm64": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.5.tgz",
- "integrity": "sha512-zXcwKlQApYAOELHd8PwKDFkagYF9Wy4e0RJ+0qnzl9Pjnpj75TEG8ufv40p2J7kCEfwZAsNiuzRIyNNMWT38ig==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.9.tgz",
+ "integrity": "sha512-YC8YsI30o606GTZi0VyzYlsDKFP8W61i/QzayHDkLbNEz/IShqAmTa+hsJRj13xTHA0H+6fk4b2UmGn+Q/cMlg==",
"cpu": [
"arm64"
],
@@ -381,9 +381,9 @@
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.5.tgz",
- "integrity": "sha512-dK4QakI42nzWgJT5sm4y4y/O//D4OxM75/cH28RLV+nzIN9AY+YsbuUVrUTjlLjXR6vpyxFbSsbmNuJ6BP9sww==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.9.tgz",
+ "integrity": "sha512-IwhlH3qK5urrY8hZiEgGkHKEFN901p/p2bjxCxJlr4GyNnF7wYpUvK+Y43uaRYuC4hpfjzbR3SJC3arX1jGvmw==",
"cpu": [
"arm64"
],
@@ -398,9 +398,9 @@
}
},
"node_modules/@rolldown/binding-darwin-x64": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.5.tgz",
- "integrity": "sha512-fqSALaUu1Wjd1nK2uW2kJDWdLCc8lx1IcY+MTY26Aurfdx19anlzhqXOgCFbBFQnlFDTn4TC1/7Nz4Bl2mLP3A==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.9.tgz",
+ "integrity": "sha512-XxpJfVzFh+jilRxIXUqcfYAYcunIc/XEzIizsOL1fcJee5Sf7H3mH8WlLmfHfluz5amqR88QQo9izKtmMlavAw==",
"cpu": [
"x64"
],
@@ -415,9 +415,9 @@
}
},
"node_modules/@rolldown/binding-freebsd-x64": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.5.tgz",
- "integrity": "sha512-/vCnNxlkxs9tKxNDcyWUePpJ/PgTzxIaVhoM5SmG8UV+GR/IcPam4VYxi7GIMo7PSDuNqlJqvprqii9NqqVCMw==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.9.tgz",
+ "integrity": "sha512-kSfvhmgeWyfkbT3p/1s5vSgboogoah2zkm9fX2zjg2hHxSV7T4KhMWRUUaRk4OXNqoD3QAUeRqLcs1aZOK4U1g==",
"cpu": [
"x64"
],
@@ -432,9 +432,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.5.tgz",
- "integrity": "sha512-abk0NLA519LxRCszmbE0jYKuQ9YPocOXTiOXOo6Yr+YAT95VH+PtqYAjOJvGKt3viEd/x4qzabAlwd5bHOOARg==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.9.tgz",
+ "integrity": "sha512-1RVzG17pxqbTfYLC352JlLt6kKLG+6Hr30n8DlIJqsnV5luUDd2Qdx9Ayw1Cabfyb1K9k0jXEZ7evxkRoT+uiw==",
"cpu": [
"arm"
],
@@ -449,9 +449,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.5.tgz",
- "integrity": "sha512-Y7eALiJ8lr0M2HH103Js+g7V34wf6snlpZLAsHI90uLhr3PVlNsbFVAXJC9d/V6BnPyKtpSwI+NcB/RLxsQxuA==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.9.tgz",
+ "integrity": "sha512-BXqPvZ2drqVD+/Z8UpKwcs4Mp7grM+eGFku4CAEKrEtcbAsUpzREphK1sogCRZGreVPiMkiiBtw0n3TPteuqvw==",
"cpu": [
"arm64"
],
@@ -469,9 +469,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.5.tgz",
- "integrity": "sha512-xMvZgnbZg4YVnR/AX2b3oOPDTFYJvUVaJg5FedA/LuvexAtXibZQej4cnTkw3rjsJ/ggUROB64TdtETiim+FYA==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.9.tgz",
+ "integrity": "sha512-11vWvo8YDwLzukt27J3aYDWU+gg2P7J+ZOmiJ0hkF5BXZDW7pVya7r40MXDy6ya0i9KamoENSVKIugvJNgFXIA==",
"cpu": [
"arm64"
],
@@ -489,9 +489,9 @@
}
},
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.5.tgz",
- "integrity": "sha512-GRjeqTUDHTo5GwntsLaAMcBahG3nlpjftXWZLN73HiYQlhwEowvarFgQnRnQZtIp4keXX7quXFbG38uPZBa2EA==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.9.tgz",
+ "integrity": "sha512-a1tijMkdwsIARtc0F39ApURROkf3NwqinI6TOiSSWCTR7dT96dffNvMUtDHnq64wKNTIZOIlzKrFvvFUznJiyw==",
"cpu": [
"ppc64"
],
@@ -509,9 +509,9 @@
}
},
"node_modules/@rolldown/binding-linux-s390x-gnu": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.5.tgz",
- "integrity": "sha512-vLNTR45F2Uwc8AufkNXPmB4VliaXs+FvcheEogIzOXzO4l+LzieXF5A/TWxLy5HtqpsRCHUfd0lPVrrdgXdLHQ==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.9.tgz",
+ "integrity": "sha512-x6SQNdAvv4c3hWqTMaWuawzMX9myaCs/yEmlGsxJzkdClnHW7FbrjQuSiRDhuSYzEYoEMhsaJy9qHG/XNemJPQ==",
"cpu": [
"s390x"
],
@@ -529,9 +529,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.5.tgz",
- "integrity": "sha512-Mgj59/HTuYeK9Gz2MA+mBWKnHsAgkBSec15ZMb1st3oIfFbX7gCjOae7GydHhzcyQi9Z/7M1QuN9bR3oFqF0jQ==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.9.tgz",
+ "integrity": "sha512-9s0AZ8BFK5/n7B/TBoa2yJE3gI3KURrbXcPBlsAsvjU4VeJKgE90y1YtNxyEUIcHPQkg6/yfF3qihUrcM/Kf0Q==",
"cpu": [
"x64"
],
@@ -549,9 +549,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-musl": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.5.tgz",
- "integrity": "sha512-mY8AP0/ichsbhAxGnLa3d3+MwV0EfgrPND2bplI3Ym8T6R2pJ0N87bvrKVwNXmdy3jnr6eQBecdqx/HMknBmpA==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.9.tgz",
+ "integrity": "sha512-P7VWAmV+WdJluH7ovnRGoiv2i8To7GAZ+kGzfGup635cyL7SyYl3lSUaA3Gp5THf0n/Co5EyEqb2zbqq+nMOHQ==",
"cpu": [
"x64"
],
@@ -569,9 +569,9 @@
}
},
"node_modules/@rolldown/binding-openharmony-arm64": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.5.tgz",
- "integrity": "sha512-8SLssA2oweAxyRgDp789ACfRb/3P+zNRJpzZxSizxF9m8NUDQ4+3xjo8ttjhVGGw6Qxb70oZiEtIjaKikCO7Yw==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.9.tgz",
+ "integrity": "sha512-1qixtsE4BK8h+yS3BfmZ09UhA7O/N4IACva6YBr7EBvCJraByTuRcgOTaiA62Tm0vey3UcKXLOaoGHtYmNGEVg==",
"cpu": [
"arm64"
],
@@ -586,9 +586,9 @@
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.5.tgz",
- "integrity": "sha512-vGbruD5zquhoc8D9SViXgN2FBJtNdTyQ4DtG+SWiEGlJiAzoKcZ2xp+xuXCffhubVdt0NJlTZqkeRuERy7g8Cw==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.9.tgz",
+ "integrity": "sha512-ok8IQjcEPs1AKZfuEUznVBrJw+gK4soq+bx8b1X2XoMqVClarc1q5JDmVtWXY1xfr6ZuHTAsPXHTgTrqKTZeww==",
"cpu": [
"arm64"
],
@@ -603,9 +603,9 @@
}
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.5.tgz",
- "integrity": "sha512-e/SXpgISz+IoqVcSSI0rx/d/he8zqLex+/rCWpnHpmVfmPIUjag9H6P7zotf0gJHwPUhQxZ/mF8tr6acebT9yw==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.9.tgz",
+ "integrity": "sha512-Ip2mXoU0hM0boq3Rf+ekuT653OROSo6aSYcPT1VHE4q52KvyxgFkQgrgb/IEsxOuvQ2fZZbs8khJAyCEPM24/g==",
"cpu": [
"x64"
],
@@ -626,13 +626,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/@standard-schema/spec": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
- "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/@types/chai": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
@@ -652,20 +645,20 @@
"license": "MIT"
},
"node_modules/@types/estree": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
- "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
+ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": {
- "version": "26.2.0",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz",
- "integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==",
+ "version": "26.6.2",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.2.tgz",
+ "integrity": "sha512-X1P21scMv4zGKLYqjdGjaKa7COa0RKVYYZZN/NfvLQ1JegxFhdhpZG/Lyn8AXx6CDUavKAd11v6BvfpkDByK8g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "undici-types": "~8.3.0"
+ "undici-types": "~8.9.0"
}
},
"node_modules/@typescript/typescript-aix-ppc64": {
@@ -1009,29 +1002,27 @@
}
},
"node_modules/@vitest/coverage-v8": {
- "version": "4.1.11",
- "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.11.tgz",
- "integrity": "sha512-8MVGEFnJIcdGjcbfKmeq8z0pZHH0JlVtoVZH9Q/qwUp6wyFnEJUBMrw9DCaj+ra3vShGmhavjalMIhPNxZAUcw==",
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-5.0.1.tgz",
+ "integrity": "sha512-FRC8ACiudC3dI6MTplzRSYWHDRnIv2IPfbzs4FdoJNsMal/35sWV8hwIfV8ZcqzSPy+uXHeMVONt9CEqtOU17w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@bcoe/v8-coverage": "^1.0.2",
- "@vitest/utils": "4.1.11",
- "ast-v8-to-istanbul": "^1.0.0",
- "istanbul-lib-coverage": "^3.2.2",
- "istanbul-lib-report": "^3.0.1",
- "istanbul-reports": "^3.2.0",
- "magicast": "^0.5.2",
- "obug": "^2.1.1",
- "std-env": "^4.0.0-rc.1",
- "tinyrainbow": "^3.1.0"
+ "@vitest/istanbul-lib-coverage": "^1.0.0",
+ "@vitest/istanbul-lib-report": "^1.0.0",
+ "ast-v8-to-istanbul": "^1.0.5",
+ "magicast": "^0.5.4",
+ "obug": "^2.1.4",
+ "std-env": "^4.2.0",
+ "tinyrainbow": "^3.1.1"
},
"funding": {
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
- "@vitest/browser": "4.1.11",
- "vitest": "4.1.11"
+ "@vitest/browser": "5.0.1",
+ "vitest": "5.0.1"
},
"peerDependenciesMeta": {
"@vitest/browser": {
@@ -1039,34 +1030,40 @@
}
}
},
- "node_modules/@vitest/expect": {
- "version": "4.1.11",
- "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.11.tgz",
- "integrity": "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==",
+ "node_modules/@vitest/istanbul-lib-coverage": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@vitest/istanbul-lib-coverage/-/istanbul-lib-coverage-1.0.1.tgz",
+ "integrity": "sha512-k3DJZ8LhMBK9NS4SclF1ASD3OgXEWDorbIcPTRDK0/Zae6fRvu+fJRxtFdLfHsa9Y24beCdPnoNZ4LviTNstfA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=22"
+ }
+ },
+ "node_modules/@vitest/istanbul-lib-report": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@vitest/istanbul-lib-report/-/istanbul-lib-report-1.0.1.tgz",
+ "integrity": "sha512-1EOLRfsTMnyAr3+kEAsP4o9dhaDlGPpD7H5iLBBeq//YpNB1VIahkPhB+eRp9N2Dkfw8oySROjE3yf9XDeaIkQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@standard-schema/spec": "^1.1.0",
- "@types/chai": "^5.2.2",
- "@vitest/spy": "4.1.11",
- "@vitest/utils": "4.1.11",
- "chai": "^6.2.2",
- "tinyrainbow": "^3.1.0"
+ "@vitest/istanbul-lib-coverage": "1.0.1"
},
- "funding": {
- "url": "https://opencollective.com/vitest"
+ "engines": {
+ "node": ">=22"
}
},
"node_modules/@vitest/mocker": {
- "version": "4.1.11",
- "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.11.tgz",
- "integrity": "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==",
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-5.0.1.tgz",
+ "integrity": "sha512-6K1DoBNAPGvuOcSsGA4D6x+5zEEff/KmOOP3uetT2TrGpVfI+HRHRnJJfKi5ib/g1vx8IYHQD8s0pbJz8WQI7Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/spy": "4.1.11",
+ "@jridgewell/trace-mapping": "0.3.31",
+ "@vitest/spy": "5.0.1",
"estree-walker": "^3.0.3",
- "magic-string": "^0.30.21"
+ "magic-string": "^1.2.3"
},
"funding": {
"url": "https://opencollective.com/vitest"
@@ -1084,78 +1081,20 @@
}
}
},
- "node_modules/@vitest/pretty-format": {
- "version": "4.1.11",
- "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.11.tgz",
- "integrity": "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "tinyrainbow": "^3.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/@vitest/runner": {
- "version": "4.1.11",
- "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.11.tgz",
- "integrity": "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@vitest/utils": "4.1.11",
- "pathe": "^2.0.3"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/@vitest/snapshot": {
- "version": "4.1.11",
- "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.11.tgz",
- "integrity": "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@vitest/pretty-format": "4.1.11",
- "@vitest/utils": "4.1.11",
- "magic-string": "^0.30.21",
- "pathe": "^2.0.3"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
"node_modules/@vitest/spy": {
- "version": "4.1.11",
- "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.11.tgz",
- "integrity": "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==",
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-5.0.1.tgz",
+ "integrity": "sha512-rbto/mF/SGERxEgYOek7Xm6B9b+y+mVoo+f4b2LymYO8zM1b7uB5nHuhVMTP2hxdzgxvGiZYGxGIaMvL5y180Q==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://opencollective.com/vitest"
}
},
- "node_modules/@vitest/utils": {
- "version": "4.1.11",
- "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.11.tgz",
- "integrity": "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@vitest/pretty-format": "4.1.11",
- "convert-source-map": "^2.0.0",
- "tinyrainbow": "^3.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
"node_modules/acorn": {
- "version": "8.17.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz",
- "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==",
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz",
+ "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==",
"dev": true,
"license": "MIT",
"bin": {
@@ -1202,9 +1141,9 @@
}
},
"node_modules/ast-v8-to-istanbul": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz",
- "integrity": "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==",
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.6.tgz",
+ "integrity": "sha512-fvpl29helSO2w/z7utIbrkNXILdrLwDwAMH2I/zPKlGf5244+gf+B4cyS1sANcrPY2h+hWCGSgC8N61s/+AF9A==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1214,9 +1153,9 @@
}
},
"node_modules/bidi-js": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
- "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.1.0.tgz",
+ "integrity": "sha512-fX1Onk0tdVPC7obPWB5EbJ1z7NVhLq4m2xZLq2YXBkxzMXIGRpNMU88n0EPgWseKl12J7zXs7qrDxPK4sRs2fg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1313,13 +1252,6 @@
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
- "node_modules/convert-source-map": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
- "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/css-tree": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz",
@@ -1348,6 +1280,21 @@
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
}
},
+ "node_modules/data-urls/node_modules/whatwg-url": {
+ "version": "16.0.1",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz",
+ "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@exodus/bytes": "^1.11.0",
+ "tr46": "^6.0.0",
+ "webidl-conversions": "^8.0.1"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ }
+ },
"node_modules/decimal.js": {
"version": "10.6.0",
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
@@ -1373,9 +1320,9 @@
"license": "MIT"
},
"node_modules/entities": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz",
- "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==",
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-8.1.0.tgz",
+ "integrity": "sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
@@ -1478,16 +1425,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/html-encoding-sniffer": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz",
@@ -1501,13 +1438,6 @@
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
}
},
- "node_modules/html-escaper": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
- "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/is-potential-custom-element-name": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
@@ -1515,45 +1445,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/istanbul-lib-coverage": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
- "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/istanbul-lib-report": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
- "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "istanbul-lib-coverage": "^3.0.0",
- "make-dir": "^4.0.0",
- "supports-color": "^7.1.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/istanbul-reports": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
- "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "html-escaper": "^2.0.0",
- "istanbul-lib-report": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/js-tokens": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz",
@@ -1562,16 +1453,16 @@
"license": "MIT"
},
"node_modules/jsdom": {
- "version": "30.0.1",
- "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.0.1.tgz",
- "integrity": "sha512-52v7mUVUfNQVYYqE1lcdaymWL0njO7lTLUog6ZvW2U5KsbiLk/GnZlVJ+qx0xfNJZ6Gn+KSpPNE52vurbxZwrA==",
+ "version": "30.1.0",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.1.0.tgz",
+ "integrity": "sha512-h/Q+H+KmlGf49ITw3TrykSVbg04S75S2O7ZtJc9gMGOWBrQRZmx5AJIKLtJ01e6Kszl+/tpFthCVqxNifxhoqg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@asamuzakjp/css-color": "^6.0.5",
- "@asamuzakjp/dom-selector": "^8.3.0",
+ "@asamuzakjp/css-color": "^7.0.0",
+ "@asamuzakjp/dom-selector": "^9.1.2",
"@bramus/specificity": "^2.4.2",
- "@csstools/css-syntax-patches-for-csstree": "^1.1.7",
+ "@csstools/css-syntax-patches-for-csstree": "^1.1.13",
"@exodus/bytes": "^1.15.1",
"css-tree": "^3.2.1",
"data-urls": "^7.0.0",
@@ -1581,13 +1472,12 @@
"lru-cache": "^11.5.2",
"parse5": "^8.0.1",
"saxes": "^6.0.0",
- "symbol-tree": "^3.2.4",
"tough-cookie": "^6.0.2",
- "undici": "^8.9.0",
+ "undici": "^8.10.2",
"w3c-xmlserializer": "^5.0.0",
"webidl-conversions": "^8.0.1",
"whatwg-mimetype": "^5.0.0",
- "whatwg-url": "^17.1.0",
+ "whatwg-url": "^17.1.1",
"xml-name-validator": "^5.0.0"
},
"engines": {
@@ -1602,21 +1492,6 @@
}
}
},
- "node_modules/jsdom/node_modules/whatwg-url": {
- "version": "17.1.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-17.1.0.tgz",
- "integrity": "sha512-3GeworPmc2ZfEEHP7lEbUfBX/L75wdEsi0rLNhXcXxnoN5jyq0SL5gCy06SGW2cyTIZdTvWIDQNQoza++vKeaw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@exodus/bytes": "^1.15.1",
- "tr46": "^6.0.0",
- "webidl-conversions": "^8.0.1"
- },
- "engines": {
- "node": "^22.14.0 || >=24.0.0"
- }
- },
"node_modules/lightningcss": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
@@ -1891,9 +1766,9 @@
}
},
"node_modules/lru-cache": {
- "version": "11.5.2",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz",
- "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==",
+ "version": "11.5.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz",
+ "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
@@ -1901,43 +1776,27 @@
}
},
"node_modules/magic-string": {
- "version": "0.30.21",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
- "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.1.tgz",
+ "integrity": "sha512-8lyCu36ErXR0J9uaGKlKQoiLZKmtI63YGLE8G2o9jyRPdr4X47LusSOwgOJOzcVtp81fTAAjxR7BwKz682Jhow==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.5"
+ "@jridgewell/sourcemap-codec": "^1.6.0"
}
},
"node_modules/magicast": {
- "version": "0.5.2",
- "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz",
- "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==",
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.5.tgz",
+ "integrity": "sha512-UicdXN8zQ3JHlxVq+28afMXPr1z7WNY6+7EJnzTdQWkTAlMLF5fNCCKxJHBQwGaNGR11581EiQmQzx73+MvszA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.29.0",
- "@babel/types": "^7.29.0",
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7",
"source-map-js": "^1.2.1"
}
},
- "node_modules/make-dir": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
- "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "semver": "^7.5.3"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/mdn-data": {
"version": "2.27.1",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz",
@@ -1946,9 +1805,9 @@
"license": "CC0-1.0"
},
"node_modules/nanoid": {
- "version": "3.3.18",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
- "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
+ "version": "3.3.19",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz",
+ "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==",
"dev": true,
"funding": [
{
@@ -1965,9 +1824,9 @@
}
},
"node_modules/obug": {
- "version": "2.1.4",
- "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz",
- "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==",
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/obug/-/obug-2.2.1.tgz",
+ "integrity": "sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==",
"dev": true,
"funding": [
"https://github.com/sponsors/sxzz",
@@ -1991,13 +1850,6 @@
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
- "node_modules/pathe": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
- "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -2006,9 +1858,9 @@
"license": "ISC"
},
"node_modules/picomatch": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
- "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2019,9 +1871,9 @@
}
},
"node_modules/postcss": {
- "version": "8.5.26",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz",
- "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==",
+ "version": "8.5.28",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz",
+ "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==",
"dev": true,
"funding": [
{
@@ -2039,7 +1891,7 @@
],
"license": "MIT",
"dependencies": {
- "nanoid": "^3.3.17",
+ "nanoid": "^3.3.18",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
@@ -2068,13 +1920,13 @@
}
},
"node_modules/rolldown": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.5.tgz",
- "integrity": "sha512-VD2IE5PUG4Oj8zz2VGykiYd5wbnjdIiSsNQb8Qu5B+noEp+A78mu2iVvpp27g8es14Tk9rofNs5Tku9iQCS4fA==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.9.tgz",
+ "integrity": "sha512-hx/Pv0N1haXRb11qkfnK5MXB/iqr7i0yjWQqmO9uHqZpBgQSqzc8UsSnEpalsh+j1I8qQ2CkXAkJC8Br3dKSlg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@oxc-project/types": "=0.146.0",
+ "@oxc-project/types": "=0.150.0",
"@rolldown/pluginutils": "^1.0.0"
},
"bin": {
@@ -2084,21 +1936,21 @@
"node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
- "@rolldown/binding-android-arm-eabi": "1.2.5",
- "@rolldown/binding-android-arm64": "1.2.5",
- "@rolldown/binding-darwin-arm64": "1.2.5",
- "@rolldown/binding-darwin-x64": "1.2.5",
- "@rolldown/binding-freebsd-x64": "1.2.5",
- "@rolldown/binding-linux-arm-gnueabihf": "1.2.5",
- "@rolldown/binding-linux-arm64-gnu": "1.2.5",
- "@rolldown/binding-linux-arm64-musl": "1.2.5",
- "@rolldown/binding-linux-ppc64-gnu": "1.2.5",
- "@rolldown/binding-linux-s390x-gnu": "1.2.5",
- "@rolldown/binding-linux-x64-gnu": "1.2.5",
- "@rolldown/binding-linux-x64-musl": "1.2.5",
- "@rolldown/binding-openharmony-arm64": "1.2.5",
- "@rolldown/binding-win32-arm64-msvc": "1.2.5",
- "@rolldown/binding-win32-x64-msvc": "1.2.5"
+ "@rolldown/binding-android-arm-eabi": "1.2.9",
+ "@rolldown/binding-android-arm64": "1.2.9",
+ "@rolldown/binding-darwin-arm64": "1.2.9",
+ "@rolldown/binding-darwin-x64": "1.2.9",
+ "@rolldown/binding-freebsd-x64": "1.2.9",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.2.9",
+ "@rolldown/binding-linux-arm64-gnu": "1.2.9",
+ "@rolldown/binding-linux-arm64-musl": "1.2.9",
+ "@rolldown/binding-linux-ppc64-gnu": "1.2.9",
+ "@rolldown/binding-linux-s390x-gnu": "1.2.9",
+ "@rolldown/binding-linux-x64-gnu": "1.2.9",
+ "@rolldown/binding-linux-x64-musl": "1.2.9",
+ "@rolldown/binding-openharmony-arm64": "1.2.9",
+ "@rolldown/binding-win32-arm64-msvc": "1.2.9",
+ "@rolldown/binding-win32-x64-msvc": "1.2.9"
}
},
"node_modules/rxjs": {
@@ -2124,19 +1976,6 @@
"node": ">=v12.22.7"
}
},
- "node_modules/semver": {
- "version": "7.7.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
- "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/shell-quote": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.9.0.tgz",
@@ -2236,30 +2075,10 @@
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
- "node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/symbol-tree": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
- "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/terser": {
- "version": "5.50.0",
- "resolved": "https://registry.npmjs.org/terser/-/terser-5.50.0.tgz",
- "integrity": "sha512-CN9BVxWhgS/hRxtUMjtC2uRWSTcSfQFHMDWma6sKKfIivCD91sM+FOPfvwoaRMqCSrUpe1nv3jDamd9eEQ4y+w==",
+ "version": "5.51.2",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-5.51.2.tgz",
+ "integrity": "sha512-bWnjSNscmuI+GJze6ZupnHP8G/cTcsJF+bXCeQknk2SHQsgbNJnLrqiH9jZ2W4STPVXH2mDKKRX3iwPhc9Cn/Q==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
@@ -2276,11 +2095,14 @@
}
},
"node_modules/tinybench": {
- "version": "2.9.0",
- "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
- "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "version": "6.1.4",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.1.4.tgz",
+ "integrity": "sha512-9APumHG7r4yOk4X4WlkmE71aZcv1gvin1czO3OQ1U9iJcFA5Ja/ygyb0vPOVHTthFozUYs8CLoLUlM8grb2lTQ==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.0.0"
+ }
},
"node_modules/tinyexec": {
"version": "1.3.0",
@@ -2320,22 +2142,22 @@
}
},
"node_modules/tldts": {
- "version": "7.4.9",
- "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.9.tgz",
- "integrity": "sha512-3kZ8wQQ/k5DrChD4X4FVvr2D7E5uoRgAqkPyLpSCGUvqOvqu+JEdr3mwMUaVWb+vMHZaKhF5fp2PBigKsui7hA==",
+ "version": "7.4.13",
+ "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.13.tgz",
+ "integrity": "sha512-iHtaIWWIbMDkCeJdTBzZFGgbluE5J+oHlb2g7+oAz1S1gpuVpabRZdQyd471Vl8UUkcz2vXSL8xZH2kyCe8tfA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "tldts-core": "^7.4.9"
+ "tldts-core": "^7.4.13"
},
"bin": {
"tldts": "bin/cli.js"
}
},
"node_modules/tldts-core": {
- "version": "7.4.9",
- "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.9.tgz",
- "integrity": "sha512-DxKfPBI52p2msTEu7MPhdpdDTBhhVQg1a/8PjQckeyAvO13eMYElX545grIp6nnTGIMZlRvFZPvFhvI/WIz2Vg==",
+ "version": "7.4.13",
+ "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.13.tgz",
+ "integrity": "sha512-mbYsrih5FRtGxs3Usvl/PqwJsNpp+jsmrdFviiK02teHDG0/HebBG/pqCylje3kzgXYzuLoHJF/0mz9W53t8Xg==",
"dev": true,
"license": "MIT"
},
@@ -2418,9 +2240,9 @@
}
},
"node_modules/undici": {
- "version": "8.9.0",
- "resolved": "https://registry.npmjs.org/undici/-/undici-8.9.0.tgz",
- "integrity": "sha512-aWZpUj7XoGonMClx4gdDRfgBjqeA+F473aDmROQQbM9n6PRfK/u1q/a0X4wMTgcHfT8H6fpbt98PFuDUwFg2YA==",
+ "version": "8.10.2",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-8.10.2.tgz",
+ "integrity": "sha512-/y4/bH9YNU5hi9NIrpOuvGXFcxrj3CMrV+/AYpowAYTpHn8gX/XPFjNy766FPoYY0miQhdW977JFWKGNhBdwyQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2428,23 +2250,23 @@
}
},
"node_modules/undici-types": {
- "version": "8.3.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz",
- "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==",
+ "version": "8.9.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz",
+ "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==",
"dev": true,
"license": "MIT"
},
"node_modules/vite": {
- "version": "8.2.2",
- "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz",
- "integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==",
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.0.tgz",
+ "integrity": "sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"lightningcss": "^1.33.0",
- "picomatch": "^4.0.5",
- "postcss": "^8.5.26",
- "rolldown": "~1.2.4",
+ "picomatch": "^4.0.7",
+ "postcss": "^8.5.28",
+ "rolldown": "~1.2.6",
"tinyglobby": "^0.2.17"
},
"bin": {
@@ -2461,7 +2283,7 @@
},
"peerDependencies": {
"@types/node": "^20.19.0 || >=22.12.0",
- "@vitejs/devtools": "^0.4.0 || ^0.5.0",
+ "@vitejs/devtools": "^0.7.1",
"esbuild": "^0.27.0 || ^0.28.0",
"jiti": ">=1.21.0",
"less": "^4.0.0",
@@ -2513,38 +2335,31 @@
}
},
"node_modules/vitest": {
- "version": "4.1.11",
- "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.11.tgz",
- "integrity": "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==",
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-5.0.1.tgz",
+ "integrity": "sha512-iA95lQbKEkvrtTkdAgnWbXfbipWiiWe/hDl2P5tMi6WFwD76G0NxXAGp/M9EOcYupeGJRr6wppMc7CoA41TQjg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/expect": "4.1.11",
- "@vitest/mocker": "4.1.11",
- "@vitest/pretty-format": "4.1.11",
- "@vitest/runner": "4.1.11",
- "@vitest/snapshot": "4.1.11",
- "@vitest/spy": "4.1.11",
- "@vitest/utils": "4.1.11",
- "es-module-lexer": "^2.0.0",
- "expect-type": "^1.3.0",
- "magic-string": "^0.30.21",
- "obug": "^2.1.1",
- "pathe": "^2.0.3",
- "picomatch": "^4.0.3",
- "std-env": "^4.0.0-rc.1",
- "tinybench": "^2.9.0",
- "tinyexec": "^1.0.2",
- "tinyglobby": "^0.2.15",
- "tinyrainbow": "^3.1.0",
- "vite": "^6.0.0 || ^7.0.0 || ^8.0.0",
+ "@types/chai": "^5.2.2",
+ "@vitest/mocker": "5.0.1",
+ "chai": "^6.2.2",
+ "es-module-lexer": "^2.3.2",
+ "expect-type": "^1.4.0",
+ "magic-string": "^1.2.3",
+ "obug": "^2.1.4",
+ "picomatch": "^4.0.7",
+ "std-env": "^4.2.0",
+ "tinybench": "6.1.4",
+ "tinyexec": "1.3.0",
+ "tinyglobby": "^0.2.17",
"why-is-node-running": "^2.3.0"
},
"bin": {
"vitest": "vitest.mjs"
},
"engines": {
- "node": "^20.0.0 || ^22.0.0 || >=24.0.0"
+ "node": "^22.12.0 || ^24.0.0 || >=26.0.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
@@ -2552,16 +2367,16 @@
"peerDependencies": {
"@edge-runtime/vm": "*",
"@opentelemetry/api": "^1.9.0",
- "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
- "@vitest/browser-playwright": "4.1.11",
- "@vitest/browser-preview": "4.1.11",
- "@vitest/browser-webdriverio": "4.1.11",
- "@vitest/coverage-istanbul": "4.1.11",
- "@vitest/coverage-v8": "4.1.11",
- "@vitest/ui": "4.1.11",
+ "@types/node": "^22.0.0 || >=24.0.0",
+ "@vitest/browser-playwright": "5.0.1",
+ "@vitest/browser-preview": "5.0.1",
+ "@vitest/browser-webdriverio": "^5.0.0-beta.5 || >=5.0.0",
+ "@vitest/coverage-istanbul": "5.0.1",
+ "@vitest/coverage-v8": "5.0.1",
+ "@vitest/ui": "5.0.1",
"happy-dom": "*",
"jsdom": "*",
- "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ "vite": "^6.4.0 || ^7.0.0 || ^8.0.0"
},
"peerDependenciesMeta": {
"@edge-runtime/vm": {
@@ -2636,18 +2451,18 @@
}
},
"node_modules/whatwg-url": {
- "version": "16.0.1",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz",
- "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==",
+ "version": "17.1.2",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-17.1.2.tgz",
+ "integrity": "sha512-TEZA+Zqxin7Jjsm2cjRohCmen5awh+hT6Zi3VZdqZlNRk7zvOI/9WpBFg/DWlA56bWnzwm6DuB8NS0EsxQH9uQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@exodus/bytes": "^1.11.0",
+ "@exodus/bytes": "^1.15.1",
"tr46": "^6.0.0",
"webidl-conversions": "^8.0.1"
},
"engines": {
- "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ "node": "^22.14.0 || >=24.0.0"
}
},
"node_modules/why-is-node-running": {
diff --git a/src/InfiniFrame.Js/package.json b/src/InfiniFrame.Js/package.json
index 07703d88c..22d1bba58 100644
--- a/src/InfiniFrame.Js/package.json
+++ b/src/InfiniFrame.Js/package.json
@@ -5,9 +5,13 @@
"author": "InfiniLore",
"license": "Apache-2.0",
"keywords": ["infiniframe", "blazor", "interop", "webview", "desktop"],
+ "type": "module",
"engines": {
"node": ">=24.15.0"
},
+ "allowScripts": {
+ "fsevents@2.3.3": true
+ },
"scripts": {
"typecheck": "tsc --noEmit",
"build": "npm run typecheck && vite build --config vite.config.prod.ts && vite build --config vite.config.dev.ts",
@@ -17,13 +21,13 @@
"test:coverage": "vitest run --coverage"
},
"devDependencies": {
- "@types/node": "^26.2.0",
- "@vitest/coverage-v8": "^4.1.11",
+ "@types/node": "^26.6.2",
+ "@vitest/coverage-v8": "^5.0.1",
"concurrently": "^10.0.5",
- "jsdom": "^30.0.1",
- "terser": "^5.50.0",
+ "jsdom": "^30.1.0",
+ "terser": "^5.51.2",
"typescript": "^7.0.2",
- "vite": "^8.2.2",
- "vitest": "^4.1.11"
+ "vite": "^8.3.0",
+ "vitest": "^5.0.1"
}
}
diff --git a/src/InfiniFrame.Js/vite.config.dev.ts b/src/InfiniFrame.Js/vite.config.dev.ts
index c7239d012..0b2809a93 100644
--- a/src/InfiniFrame.Js/vite.config.dev.ts
+++ b/src/InfiniFrame.Js/vite.config.dev.ts
@@ -1,7 +1,7 @@
import {defineConfig} from "vite";
import {resolve} from "node:path";
-const entry = resolve(__dirname, "TypeScript/Index.ts");
+const entry = resolve(import.meta.dirname, "TypeScript/Index.ts");
export default defineConfig({
build: {
diff --git a/src/InfiniFrame.Js/vite.config.prod.ts b/src/InfiniFrame.Js/vite.config.prod.ts
index 23d36a9ee..ddeca7bb7 100644
--- a/src/InfiniFrame.Js/vite.config.prod.ts
+++ b/src/InfiniFrame.Js/vite.config.prod.ts
@@ -1,7 +1,7 @@
import {defineConfig} from "vite";
import {resolve} from "node:path";
-const entry = resolve(__dirname, "TypeScript/Index.ts");
+const entry = resolve(import.meta.dirname, "TypeScript/Index.ts");
export default defineConfig({
build: {
diff --git a/src/InfiniFrame.NativeBridge/InfiniFrame.NativeBridge.Native.targets b/src/InfiniFrame.NativeBridge/InfiniFrame.NativeBridge.Native.targets
index 772f9f26a..e6729076e 100644
--- a/src/InfiniFrame.NativeBridge/InfiniFrame.NativeBridge.Native.targets
+++ b/src/InfiniFrame.NativeBridge/InfiniFrame.NativeBridge.Native.targets
@@ -58,7 +58,7 @@
@@ -67,16 +67,16 @@
+ Exclude="Native/build/**/*;Native/packages/**/*;Native/src/Embedded/InfiniFrameJs/**/*" />
+ Exclude="Native/build/**/*;Native/packages/**/*;Native/src/Embedded/InfiniFrameJs/**/*" />
+ Exclude="Native/build/**/*;Native/packages/**/*;Native/src/Embedded/InfiniFrameJs/**/*" />
+ Exclude="Native/build/**/*;Native/packages/**/*" />
+ Exclude="Native/build/**/*;Native/packages/**/*" />
diff --git a/src/InfiniFrame.NativeBridge/InfiniFrame.NativeBridge.csproj b/src/InfiniFrame.NativeBridge/InfiniFrame.NativeBridge.csproj
index e129f9500..9758583db 100644
--- a/src/InfiniFrame.NativeBridge/InfiniFrame.NativeBridge.csproj
+++ b/src/InfiniFrame.NativeBridge/InfiniFrame.NativeBridge.csproj
@@ -2,17 +2,11 @@
InfiniLore.InfiniFrame.NativeBridge
- Library
true
- true
true
C++ native bridge layer for InfiniFrame. Provides the P/Invoke interop between .NET and the native window implementations (WebView2, WebKitGTK, WKWebView).
-
-
-
-
diff --git a/src/InfiniFrame.NativeBridge/Managed/Handles/NativeApplicationHandle.cs b/src/InfiniFrame.NativeBridge/Managed/Handles/NativeApplicationHandle.cs
new file mode 100644
index 000000000..c022dc166
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Managed/Handles/NativeApplicationHandle.cs
@@ -0,0 +1,27 @@
+using Microsoft.Win32.SafeHandles;
+
+namespace InfiniFrame.NativeBridge.Handles;
+
+/// Owns a native InfiniFrame application instance.
+public sealed class NativeApplicationHandle : SafeHandleZeroOrMinusOneIsInvalid {
+ private int _releaseStatus = (int)InfiniFrameNativeInteropStatus.Success;
+
+ internal NativeApplicationHandle(IntPtr handle) : base(true) => SetHandle(handle);
+
+ internal InfiniFrameNativeInteropStatus ReleaseStatus
+ => (InfiniFrameNativeInteropStatus)Volatile.Read(ref _releaseStatus);
+
+ internal bool TryRelease() {
+ if (IsClosed) return ReleaseStatus == InfiniFrameNativeInteropStatus.Success;
+
+ bool released = ReleaseHandle();
+ if (released) SetHandleAsInvalid();
+ return released;
+ }
+
+ protected override bool ReleaseHandle() {
+ InfiniFrameNativeInteropStatus status = InfiniFrameNative.ApplicationDestructor(handle);
+ Volatile.Write(ref _releaseStatus, (int)status);
+ return status == InfiniFrameNativeInteropStatus.Success;
+ }
+}
diff --git a/src/InfiniFrame.NativeBridge/Managed/NativeApi/Exports/InfiniFrameNative.Lifecycle.cs b/src/InfiniFrame.NativeBridge/Managed/NativeApi/Exports/InfiniFrameNative.Lifecycle.cs
index d1176d5c3..0c095e7d2 100644
--- a/src/InfiniFrame.NativeBridge/Managed/NativeApi/Exports/InfiniFrameNative.Lifecycle.cs
+++ b/src/InfiniFrame.NativeBridge/Managed/NativeApi/Exports/InfiniFrameNative.Lifecycle.cs
@@ -3,13 +3,70 @@
// ---------------------------------------------------------------------------------------------------------------------
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-using InfiniFrame.NativeBridge.Parameters;
+using InfiniFrame.NativeBridge.Parameters.Application;
+using InfiniFrame.NativeBridge.Parameters.Window;
namespace InfiniFrame.NativeBridge;
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
public partial class InfiniFrameNative {
+ [LibraryImport(ArtifactManifest.NativeLibraryName, EntryPoint = "InfiniFrameNativeApplication_ctor", SetLastError = true)]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ private static partial InfiniFrameNativeInteropStatus ApplicationConstructorNative(out IntPtr value);
+
+ internal static InfiniFrameNativeInteropStatus ApplicationConstructor(out IntPtr value)
+ => ApplicationConstructorNative(out value);
+
+ [LibraryImport(ArtifactManifest.NativeLibraryName, EntryPoint = "InfiniFrameNativeApplication_Register", SetLastError = true)]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ internal static partial InfiniFrameNativeInteropStatus ApplicationRegister(IntPtr instance);
+
+ [LibraryImport(ArtifactManifest.NativeLibraryName, EntryPoint = "InfiniFrameNativeApplication_Configure", SetLastError = true)]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ private static partial InfiniFrameNativeInteropStatus ApplicationConfigureNative(IntPtr instance, IntPtr parameters);
+
+ internal static InfiniFrameNativeInteropStatus ApplicationConfigure(
+ IntPtr instance,
+ in InfiniFrameNativeApplicationParameters parameters
+ ) {
+ var marshaller = new InfiniFrameNativeApplicationParametersMarshaller.ManagedToUnmanagedIn();
+ marshaller.FromManaged(parameters);
+ IntPtr unmanagedPtr = IntPtr.Zero;
+
+ try {
+ unmanagedPtr = Marshal.AllocHGlobal(Marshal.SizeOf());
+ Marshal.StructureToPtr(marshaller.ToUnmanaged(), unmanagedPtr, false);
+ return ApplicationConfigureNative(instance, unmanagedPtr);
+ }
+ finally {
+ if (unmanagedPtr != IntPtr.Zero) Marshal.FreeHGlobal(unmanagedPtr);
+ marshaller.Free();
+ }
+ }
+
+ [LibraryImport(ArtifactManifest.NativeLibraryName, EntryPoint = "InfiniFrameNativeApplication_Run", SetLastError = true)]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ internal static partial InfiniFrameNativeInteropStatus ApplicationRun(IntPtr instance);
+
+ [LibraryImport(ArtifactManifest.NativeLibraryName, EntryPoint = "InfiniFrameNativeApplication_Shutdown", SetLastError = true)]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ internal static partial InfiniFrameNativeInteropStatus ApplicationShutdown(IntPtr instance);
+
+ [LibraryImport(ArtifactManifest.NativeLibraryName, EntryPoint = "InfiniFrameNativeApplication_dtor")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ internal static partial InfiniFrameNativeInteropStatus ApplicationDestructor(IntPtr instance);
+
+ [LibraryImport(ArtifactManifest.NativeLibraryName, EntryPoint = "InfiniFrameNativeApplication_GetWindowCount", SetLastError = true)]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ private static partial InfiniFrameNativeInteropStatus ApplicationGetWindowCountNative(
+ IntPtr instance,
+ out nuint value
+ );
+
+ internal static InfiniFrameNativeInteropStatus ApplicationGetWindowCount(IntPtr instance, out nuint value)
+ => ApplicationGetWindowCountNative(instance, out value);
+
///
/// Creates a new native window instance with the specified parameters.
///
@@ -21,16 +78,16 @@ public partial class InfiniFrameNative {
private static partial InfiniFrameNativeInteropStatus ConstructorNative(IntPtr parameters, out IntPtr value);
internal static InfiniFrameNativeInteropStatus Constructor(
- in InfiniFrameNativeParameters parameters,
+ in InfiniFrameNativeWindowParameters parameters,
out IntPtr value
) {
- var marshaller = new InfiniFrameNativeParametersMarshaller.ManagedToUnmanagedIn();
+ var marshaller = new InfiniFrameNativeWindowParametersMarshaller.ManagedToUnmanagedIn();
marshaller.FromManaged(parameters);
var unmanaged = marshaller.ToUnmanaged();
IntPtr unmanagedPtr = IntPtr.Zero;
try {
- unmanagedPtr = Marshal.AllocHGlobal(Marshal.SizeOf());
+ unmanagedPtr = Marshal.AllocHGlobal(Marshal.SizeOf());
Marshal.StructureToPtr(unmanaged, unmanagedPtr, false);
return ConstructorNative(unmanagedPtr, out value);
}
@@ -71,6 +128,10 @@ out IntPtr value
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
internal static partial InfiniFrameNativeInteropStatus SetReadyCallback(IntPtr instance, ContextAction callback, IntPtr context);
+ [LibraryImport(ArtifactManifest.NativeLibraryName, EntryPoint = "InfiniFrameNative_SetReadyFailureCallback", SetLastError = true)]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ internal static partial InfiniFrameNativeInteropStatus SetReadyFailureCallback(IntPtr instance, ContextAction callback, IntPtr context);
+
[LibraryImport(ArtifactManifest.NativeLibraryName, EntryPoint = "InfiniFrameNative_SetTeardownCallback", SetLastError = true)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
internal static partial InfiniFrameNativeInteropStatus SetTeardownCallback(IntPtr instance, ContextAction callback, IntPtr context);
diff --git a/src/InfiniFrame.NativeBridge/Managed/NativeApi/Testing/InfiniFrameNative.Testing.cs b/src/InfiniFrame.NativeBridge/Managed/NativeApi/Testing/InfiniFrameNative.Testing.cs
index 708f605f4..cf09c89a1 100644
--- a/src/InfiniFrame.NativeBridge/Managed/NativeApi/Testing/InfiniFrameNative.Testing.cs
+++ b/src/InfiniFrame.NativeBridge/Managed/NativeApi/Testing/InfiniFrameNative.Testing.cs
@@ -3,7 +3,8 @@
// ---------------------------------------------------------------------------------------------------------------------
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-using InfiniFrame.NativeBridge.Parameters;
+using InfiniFrame.NativeBridge.Parameters.Application;
+using InfiniFrame.NativeBridge.Parameters.Window;
namespace InfiniFrame.NativeBridge;
// ---------------------------------------------------------------------------------------------------------------------
@@ -62,21 +63,21 @@ out int valid
private static partial InfiniFrameNativeInteropStatus IsColorSchemeChangeNative(IntPtr lParam, out int result);
///
- /// Returns a native pointer to a newly allocated InfiniFrameInitParams clone.
+ /// Returns a native pointer to a newly allocated InfiniFrameWindowInitParams clone.
/// Ownership is transferred to managed caller, which must call exactly once.
///
/// The parameters to clone.
/// The native pointer to the cloned parameters.
/// A status code indicating success or failure.
- internal static InfiniFrameNativeInteropStatus NativeParametersReturnAsIsPtr(ref InfiniFrameNativeParameters parameters, out IntPtr newParametersPtr) {
- var marshaller = new InfiniFrameNativeParametersMarshaller.ManagedToUnmanagedIn();
+ internal static InfiniFrameNativeInteropStatus NativeParametersReturnAsIsPtr(ref InfiniFrameNativeWindowParameters parameters, out IntPtr newParametersPtr) {
+ var marshaller = new InfiniFrameNativeWindowParametersMarshaller.ManagedToUnmanagedIn();
marshaller.FromManaged(parameters);
var unmanaged = marshaller.ToUnmanaged();
InfiniFrameNativeInteropStatus status;
IntPtr unmanagedPtr = IntPtr.Zero;
try {
- unmanagedPtr = Marshal.AllocHGlobal(Marshal.SizeOf());
+ unmanagedPtr = Marshal.AllocHGlobal(Marshal.SizeOf());
Marshal.StructureToPtr(unmanaged, unmanagedPtr, false);
status = NativeParametersReturnAsIsNative(unmanagedPtr, out newParametersPtr);
}
@@ -91,6 +92,41 @@ internal static InfiniFrameNativeInteropStatus NativeParametersReturnAsIsPtr(ref
return status;
}
+ [LibraryImport(ArtifactManifest.NativeLibraryName, EntryPoint = "InfiniFrameNativeTests_NativeApplicationParametersReturnAsIs", SetLastError = true)]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ private static partial InfiniFrameNativeInteropStatus NativeApplicationParametersReturnAsIsNative(IntPtr parameters, out IntPtr newParameters);
+
+ internal static InfiniFrameNativeInteropStatus NativeApplicationParametersReturnAsIsPtr(
+ ref InfiniFrameNativeApplicationParameters parameters,
+ out IntPtr newParametersPtr
+ ) {
+ var marshaller = new InfiniFrameNativeApplicationParametersMarshaller.ManagedToUnmanagedIn();
+ marshaller.FromManaged(parameters);
+ var unmanaged = marshaller.ToUnmanaged();
+ InfiniFrameNativeInteropStatus status;
+ IntPtr unmanagedPtr = IntPtr.Zero;
+
+ try {
+ unmanagedPtr = Marshal.AllocHGlobal(Marshal.SizeOf());
+ Marshal.StructureToPtr(unmanaged, unmanagedPtr, false);
+ status = NativeApplicationParametersReturnAsIsNative(unmanagedPtr, out newParametersPtr);
+ }
+ finally {
+ if (unmanagedPtr != IntPtr.Zero) Marshal.FreeHGlobal(unmanagedPtr);
+ marshaller.Free();
+ }
+
+ if (newParametersPtr == IntPtr.Zero) throw new InvalidOperationException("Native function returned null pointer");
+ return status;
+ }
+
+ internal static InfiniFrameNativeInteropStatus FreeApplicationParameters(IntPtr parameters)
+ => FreeApplicationParametersNative(parameters);
+
+ [LibraryImport(ArtifactManifest.NativeLibraryName, EntryPoint = "InfiniFrameNativeTests_FreeApplicationParameters", SetLastError = true)]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ private static partial InfiniFrameNativeInteropStatus FreeApplicationParametersNative(IntPtr parameters);
+
///
/// Frees init parameters that were allocated by native code during testing.
///
diff --git a/src/InfiniFrame.NativeBridge/Managed/NativeInvoke.cs b/src/InfiniFrame.NativeBridge/Managed/NativeInvoke.cs
index 1815ff675..ad153cd25 100644
--- a/src/InfiniFrame.NativeBridge/Managed/NativeInvoke.cs
+++ b/src/InfiniFrame.NativeBridge/Managed/NativeInvoke.cs
@@ -71,7 +71,7 @@ internal static partial class NativeInvoke {
// Otherwise, we need to execute it on the window thread.
else {
- logger.LogTrace("Executing callback on window thread. Marshalling to C++ native cobebase.");
+ logger.LogTrace("Executing callback on window thread. Marshalling to C++ native cobebase");
string? nativeErrorMessage = null;
InfiniFrameNative.Invoke(nativeHandle, callback: () => {
try {
@@ -141,7 +141,7 @@ private static void EnsureSuccess(ILogger logger, InfiniFrameNativeInteropStatus
// The explicit interop status is authoritative. A managed callback executed inside a native dispatch can
// leave an unrelated Win32 last-error value on the thread even though the enclosing operation succeeded.
Marshal.SetLastPInvokeError(0);
- logger.LogTrace("Native interop call succeeded.");
+ logger.LogTrace("Native interop call succeeded");
return;
}
@@ -163,7 +163,7 @@ private static void EnsureSuccess(ILogger logger, InfiniFrameNativeInteropStatus
message = foundMessage;
}
else {
- logger.LogTrace("Native interop call failed with no error message.");
+ logger.LogTrace("Native interop call failed with no error message");
message = NoNativeMessage;
}
diff --git a/src/InfiniFrame.NativeBridge/Managed/Parameters/Application/InfiniFrameNativeApplicationParameters.cs b/src/InfiniFrame.NativeBridge/Managed/Parameters/Application/InfiniFrameNativeApplicationParameters.cs
new file mode 100644
index 000000000..324ff7dd5
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Managed/Parameters/Application/InfiniFrameNativeApplicationParameters.cs
@@ -0,0 +1,29 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+using System.Runtime.InteropServices;
+
+namespace InfiniFrame.NativeBridge.Parameters.Application;
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+///
+/// Process-wide configuration passed to the native InfiniFrame application.
+/// Field order is part of the managed/native ABI. Append fields before only.
+///
+[StructLayout(LayoutKind.Sequential)]
+public struct InfiniFrameNativeApplicationParameters() {
+ [MarshalAs(UnmanagedType.LPUTF8Str)]
+ internal string? WebView2RuntimePath;
+
+ [MarshalAs(UnmanagedType.LPUTF8Str)]
+ internal string? NotificationRegistrationId;
+
+ [MarshalAs(UnmanagedType.LPUTF8Str)]
+ internal string? AppUserModelId;
+
+ [MarshalAs(UnmanagedType.LPUTF8Str)]
+ internal string? DefaultNotificationIcon;
+
+ internal readonly int Size = Marshal.SizeOf();
+}
diff --git a/src/InfiniFrame.NativeBridge/Managed/Parameters/Application/InfiniFrameNativeApplicationParametersEqualityComparer.cs b/src/InfiniFrame.NativeBridge/Managed/Parameters/Application/InfiniFrameNativeApplicationParametersEqualityComparer.cs
new file mode 100644
index 000000000..48e995cc8
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Managed/Parameters/Application/InfiniFrameNativeApplicationParametersEqualityComparer.cs
@@ -0,0 +1,34 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+namespace InfiniFrame.NativeBridge.Parameters.Application;
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+///
+/// Compares native application parameters by their configuration values and ABI size.
+///
+internal sealed class InfiniFrameNativeApplicationParametersEqualityComparer
+ : IEqualityComparer {
+ internal static readonly InfiniFrameNativeApplicationParametersEqualityComparer Instance = new();
+
+ private InfiniFrameNativeApplicationParametersEqualityComparer() {}
+
+ public bool Equals(
+ InfiniFrameNativeApplicationParameters x,
+ InfiniFrameNativeApplicationParameters y
+ ) => x.WebView2RuntimePath == y.WebView2RuntimePath
+ && x.NotificationRegistrationId == y.NotificationRegistrationId
+ && x.AppUserModelId == y.AppUserModelId
+ && x.DefaultNotificationIcon == y.DefaultNotificationIcon
+ && x.Size == y.Size;
+
+ public int GetHashCode(InfiniFrameNativeApplicationParameters obj)
+ => HashCode.Combine(
+ obj.WebView2RuntimePath,
+ obj.NotificationRegistrationId,
+ obj.AppUserModelId,
+ obj.DefaultNotificationIcon,
+ obj.Size
+ );
+}
diff --git a/src/InfiniFrame.NativeBridge/Managed/Parameters/Application/InfiniFrameNativeApplicationParametersMarshaller.cs b/src/InfiniFrame.NativeBridge/Managed/Parameters/Application/InfiniFrameNativeApplicationParametersMarshaller.cs
new file mode 100644
index 000000000..f14ee006b
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Managed/Parameters/Application/InfiniFrameNativeApplicationParametersMarshaller.cs
@@ -0,0 +1,52 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
+
+namespace InfiniFrame.NativeBridge.Parameters.Application;
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+[CustomMarshaller(
+ typeof(InfiniFrameNativeApplicationParameters),
+ MarshalMode.ManagedToUnmanagedIn,
+ typeof(ManagedToUnmanagedIn)
+)]
+internal static class InfiniFrameNativeApplicationParametersMarshaller {
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct Unmanaged {
+ internal IntPtr WebView2RuntimePath;
+ internal IntPtr NotificationRegistrationId;
+ internal IntPtr AppUserModelId;
+ internal IntPtr DefaultNotificationIcon;
+ internal int Size;
+ }
+
+ internal ref struct ManagedToUnmanagedIn {
+ private Unmanaged _unmanaged;
+
+ public void FromManaged(InfiniFrameNativeApplicationParameters managed) {
+ _unmanaged = new Unmanaged {
+ WebView2RuntimePath = ToUtf8Ptr(managed.WebView2RuntimePath),
+ NotificationRegistrationId = ToUtf8Ptr(managed.NotificationRegistrationId),
+ AppUserModelId = ToUtf8Ptr(managed.AppUserModelId),
+ DefaultNotificationIcon = ToUtf8Ptr(managed.DefaultNotificationIcon),
+ Size = managed.Size
+ };
+ }
+
+ public Unmanaged ToUnmanaged() => _unmanaged;
+
+ public void Free() {
+ Marshal.FreeCoTaskMem(_unmanaged.WebView2RuntimePath);
+ Marshal.FreeCoTaskMem(_unmanaged.NotificationRegistrationId);
+ Marshal.FreeCoTaskMem(_unmanaged.AppUserModelId);
+ Marshal.FreeCoTaskMem(_unmanaged.DefaultNotificationIcon);
+ }
+
+ private static IntPtr ToUtf8Ptr(string? value) => value is null
+ ? IntPtr.Zero
+ : Marshal.StringToCoTaskMemUTF8(value);
+ }
+}
diff --git a/src/InfiniFrame.NativeBridge/Managed/Parameters/Application/InfiniFrameNativeApplicationParametersValidator.cs b/src/InfiniFrame.NativeBridge/Managed/Parameters/Application/InfiniFrameNativeApplicationParametersValidator.cs
new file mode 100644
index 000000000..28f94b782
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Managed/Parameters/Application/InfiniFrameNativeApplicationParametersValidator.cs
@@ -0,0 +1,20 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+using System.Runtime.InteropServices;
+using FluentValidation;
+
+namespace InfiniFrame.NativeBridge.Parameters.Application;
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+///
+/// Validates instances.
+///
+public sealed class InfiniFrameNativeApplicationParametersValidator
+ : AbstractValidator {
+ public InfiniFrameNativeApplicationParametersValidator() {
+ RuleFor(parameters => parameters.Size)
+ .Equal(Marshal.SizeOf());
+ }
+}
diff --git a/src/InfiniFrame.NativeBridge/Managed/Parameters/CustomSchemeNameMemory.cs b/src/InfiniFrame.NativeBridge/Managed/Parameters/Window/CustomSchemeNameMemory.cs
similarity index 98%
rename from src/InfiniFrame.NativeBridge/Managed/Parameters/CustomSchemeNameMemory.cs
rename to src/InfiniFrame.NativeBridge/Managed/Parameters/Window/CustomSchemeNameMemory.cs
index 84123a977..124840b18 100644
--- a/src/InfiniFrame.NativeBridge/Managed/Parameters/CustomSchemeNameMemory.cs
+++ b/src/InfiniFrame.NativeBridge/Managed/Parameters/Window/CustomSchemeNameMemory.cs
@@ -3,7 +3,7 @@
// ---------------------------------------------------------------------------------------------------------------------
using System.Runtime.InteropServices;
-namespace InfiniFrame.NativeBridge.Parameters;
+namespace InfiniFrame.NativeBridge.Parameters.Window;
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
diff --git a/src/InfiniFrame.NativeBridge/Managed/Parameters/InfiniFrameNativeParameters.cs b/src/InfiniFrame.NativeBridge/Managed/Parameters/Window/InfiniFrameNativeWindowParameters.cs
similarity index 95%
rename from src/InfiniFrame.NativeBridge/Managed/Parameters/InfiniFrameNativeParameters.cs
rename to src/InfiniFrame.NativeBridge/Managed/Parameters/Window/InfiniFrameNativeWindowParameters.cs
index 2c20201e5..ec6a6fdda 100644
--- a/src/InfiniFrame.NativeBridge/Managed/Parameters/InfiniFrameNativeParameters.cs
+++ b/src/InfiniFrame.NativeBridge/Managed/Parameters/Window/InfiniFrameNativeWindowParameters.cs
@@ -4,7 +4,7 @@
using System.Runtime.InteropServices;
using InfiniFrame.NativeBridge.Delegates;
-namespace InfiniFrame.NativeBridge.Parameters;
+namespace InfiniFrame.NativeBridge.Parameters.Window;
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
@@ -16,7 +16,7 @@ namespace InfiniFrame.NativeBridge.Parameters;
/// Passed to the native layer as a sequentially laid-out struct.
///
[StructLayout(LayoutKind.Sequential)]
-public struct InfiniFrameNativeParameters() {
+public struct InfiniFrameNativeWindowParameters() {
// Content strings
///
@@ -70,20 +70,6 @@ public struct InfiniFrameNativeParameters() {
[MarshalAs(UnmanagedType.LPUTF8Str)]
internal string? BrowserControlInitParameters;
- ///
- /// WINDOWS ONLY: OPTIONAL: Path to an extracted fixed-version WebView2 runtime used when the window is created.
- ///
- [MarshalAs(UnmanagedType.LPUTF8Str)]
- internal string? WebView2RuntimePath;
-
- ///WINDOWS: OPTIONAL: Registers the application for toast notifications. If not provided, use Window Title.
- [MarshalAs(UnmanagedType.LPUTF8Str)]
- internal string? NotificationRegistrationId;
-
- ///WINDOWS: OPTIONAL: Explicit application identity used by the taskbar for grouping and pinning.
- [MarshalAs(UnmanagedType.LPUTF8Str)]
- internal string? WindowsAppUserModelId;
-
///
/// OPTIONAL: Default icon path applied to notifications when IconPath is not specified.
///
@@ -103,6 +89,9 @@ public struct InfiniFrameNativeParameters() {
///
internal IntPtr NativeParent;
+ /// Process-scoped application owner for this native window.
+ internal IntPtr ApplicationInstance;
+
// Event callbacks
///Set by InfiniFrameOptionsBuilder
@@ -401,5 +390,5 @@ public struct InfiniFrameNativeParameters() {
/// construction.
///
[MarshalAs(UnmanagedType.I4)]
- internal readonly int Size = Marshal.SizeOf();
+ internal readonly int Size = Marshal.SizeOf();
}
diff --git a/src/InfiniFrame.NativeBridge/Managed/Parameters/InfiniFrameNativeParametersEqualityComparer.cs b/src/InfiniFrame.NativeBridge/Managed/Parameters/Window/InfiniFrameNativeWindowParametersEqualityComparer.cs
similarity index 88%
rename from src/InfiniFrame.NativeBridge/Managed/Parameters/InfiniFrameNativeParametersEqualityComparer.cs
rename to src/InfiniFrame.NativeBridge/Managed/Parameters/Window/InfiniFrameNativeWindowParametersEqualityComparer.cs
index a2efd00cb..9d821d1fc 100644
--- a/src/InfiniFrame.NativeBridge/Managed/Parameters/InfiniFrameNativeParametersEqualityComparer.cs
+++ b/src/InfiniFrame.NativeBridge/Managed/Parameters/Window/InfiniFrameNativeWindowParametersEqualityComparer.cs
@@ -1,32 +1,32 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-namespace InfiniFrame.NativeBridge.Parameters;
+namespace InfiniFrame.NativeBridge.Parameters.Window;
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
///
-/// Compares two instances for value equality,
+/// Compares two instances for value equality,
/// ignoring callback handler fields. This comparer is intended for parameter-change
/// detection where two instances with different callbacks but identical configuration
/// values are considered equivalent.
///
-internal sealed class InfiniFrameNativeParametersEqualityComparer : IEqualityComparer {
+internal sealed class InfiniFrameNativeWindowParametersEqualityComparer : IEqualityComparer {
///
/// Singleton instance of the equality comparer.
///
- internal static readonly InfiniFrameNativeParametersEqualityComparer Instance = new();
+ internal static readonly InfiniFrameNativeWindowParametersEqualityComparer Instance = new();
- private InfiniFrameNativeParametersEqualityComparer() {}
+ private InfiniFrameNativeWindowParametersEqualityComparer() {}
///
- /// Determines whether two instances are equal
+/// Determines whether two instances are equal
/// by comparing all value fields.
///
/// The first instance.
/// The second instance.
/// true if the instances are equal; otherwise, false.
- public bool Equals(InfiniFrameNativeParameters x, InfiniFrameNativeParameters y) {
+ public bool Equals(InfiniFrameNativeWindowParameters x, InfiniFrameNativeWindowParameters y) {
// Handlers are not checked because they are set by the constructor and are not user-configurable.
// x.ClosingHandler == y.ClosingHandler
// && x.ClosedHandler == y.ClosedHandler
@@ -51,9 +51,6 @@ public bool Equals(InfiniFrameNativeParameters x, InfiniFrameNativeParameters y)
if (x.TemporaryFilesPath != y.TemporaryFilesPath) return false;
if (x.UserAgent != y.UserAgent) return false;
if (x.BrowserControlInitParameters != y.BrowserControlInitParameters) return false;
- if (x.WebView2RuntimePath != y.WebView2RuntimePath) return false;
- if (x.NotificationRegistrationId != y.NotificationRegistrationId) return false;
- if (x.WindowsAppUserModelId != y.WindowsAppUserModelId) return false;
if (x.DefaultNotificationIcon != y.DefaultNotificationIcon) return false;
// Runtime configuration
@@ -126,12 +123,12 @@ public bool Equals(InfiniFrameNativeParameters x, InfiniFrameNativeParameters y)
}
///
- /// Returns a hash code for the specified instance
+/// Returns a hash code for the specified instance
/// based on its value fields.
///
/// The instance to hash.
/// A hash code value.
- public int GetHashCode(InfiniFrameNativeParameters obj) {
+ public int GetHashCode(InfiniFrameNativeWindowParameters obj) {
var hashCode = new HashCode();
// Content strings
@@ -144,9 +141,6 @@ public int GetHashCode(InfiniFrameNativeParameters obj) {
hashCode.Add(obj.TemporaryFilesPath);
hashCode.Add(obj.UserAgent);
hashCode.Add(obj.BrowserControlInitParameters);
- hashCode.Add(obj.WebView2RuntimePath);
- hashCode.Add(obj.NotificationRegistrationId);
- hashCode.Add(obj.WindowsAppUserModelId);
hashCode.Add(obj.DefaultNotificationIcon);
// Runtime configuration
diff --git a/src/InfiniFrame.NativeBridge/Managed/Parameters/InfiniFrameNativeParametersMarshaller.cs b/src/InfiniFrame.NativeBridge/Managed/Parameters/Window/InfiniFrameNativeWindowParametersMarshaller.cs
similarity index 93%
rename from src/InfiniFrame.NativeBridge/Managed/Parameters/InfiniFrameNativeParametersMarshaller.cs
rename to src/InfiniFrame.NativeBridge/Managed/Parameters/Window/InfiniFrameNativeWindowParametersMarshaller.cs
index 07bd05afe..4d86a6589 100644
--- a/src/InfiniFrame.NativeBridge/Managed/Parameters/InfiniFrameNativeParametersMarshaller.cs
+++ b/src/InfiniFrame.NativeBridge/Managed/Parameters/Window/InfiniFrameNativeWindowParametersMarshaller.cs
@@ -6,20 +6,20 @@
using System.Runtime.InteropServices.Marshalling;
using InfiniFrame.NativeBridge.Delegates;
-namespace InfiniFrame.NativeBridge.Parameters;
+namespace InfiniFrame.NativeBridge.Parameters.Window;
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
///
-/// Custom marshaller for converting
+/// Custom marshaller for converting
/// to an unmanaged representation for native interop calls.
///
[CustomMarshaller(
- typeof(InfiniFrameNativeParameters),
+ typeof(InfiniFrameNativeWindowParameters),
MarshalMode.ManagedToUnmanagedIn,
typeof(ManagedToUnmanagedIn)
)]
-internal static class InfiniFrameNativeParametersMarshaller {
+internal static class InfiniFrameNativeWindowParametersMarshaller {
// -----------------------------------------------------------------------------------------------------------------
// Methods
@@ -71,8 +71,8 @@ private static IntPtr GetCustomSchemeName(IntPtr[]? values, int index)
=> values is not null && values.Length > index ? values[index] : IntPtr.Zero;
///
- /// Unmanaged layout of used for native interop.
- /// Field order must match the C++ InfiniFrameInitParams struct exactly.
+/// Unmanaged layout of used for native interop.
+/// Field order must match the C++ InfiniFrameWindowInitParams struct exactly.
///
[StructLayout(LayoutKind.Sequential)]
internal struct Unmanaged {
@@ -86,9 +86,6 @@ internal struct Unmanaged {
internal IntPtr TemporaryFilesPath;
internal IntPtr UserAgent;
internal IntPtr BrowserControlInitParameters;
- internal IntPtr WebView2RuntimePath;
- internal IntPtr NotificationRegistrationId;
- internal IntPtr WindowsAppUserModelId;
internal IntPtr DefaultNotificationIcon;
// Runtime configuration
@@ -96,6 +93,7 @@ internal struct Unmanaged {
// Parent window
internal IntPtr NativeParent;
+ internal IntPtr ApplicationInstance;
// Event callbacks
internal IntPtr ClosingHandler;
@@ -186,7 +184,7 @@ internal struct Unmanaged {
}
///
- /// Marshals managed to the native layout.
+/// Marshals managed to the native layout.
///
[SuppressMessage("ReSharper", "NotAccessedField.Local")]
internal ref struct ManagedToUnmanagedIn {
@@ -212,7 +210,7 @@ internal ref struct ManagedToUnmanagedIn {
/// Copies all values from the managed source into the unmanaged representation.
///
/// The managed parameters source.
- public void FromManaged(InfiniFrameNativeParameters managed) {
+ public void FromManaged(InfiniFrameNativeWindowParameters managed) {
// Retain delegate references to prevent GC during native constructor call.
_closingHandler = managed.ClosingHandler;
_closedHandler = managed.ClosedHandler;
@@ -240,9 +238,6 @@ public void FromManaged(InfiniFrameNativeParameters managed) {
TemporaryFilesPath = ToUtf8Ptr(managed.TemporaryFilesPath),
UserAgent = ToUtf8Ptr(managed.UserAgent),
BrowserControlInitParameters = ToUtf8Ptr(managed.BrowserControlInitParameters),
- WebView2RuntimePath = ToUtf8Ptr(managed.WebView2RuntimePath),
- NotificationRegistrationId = ToUtf8Ptr(managed.NotificationRegistrationId),
- WindowsAppUserModelId = ToUtf8Ptr(managed.WindowsAppUserModelId),
DefaultNotificationIcon = ToUtf8Ptr(managed.DefaultNotificationIcon),
// Runtime configuration
@@ -250,6 +245,7 @@ public void FromManaged(InfiniFrameNativeParameters managed) {
// Parent window
NativeParent = managed.NativeParent,
+ ApplicationInstance = managed.ApplicationInstance,
// Event callbacks
ClosingHandler = ToFunctionPtr(managed.ClosingHandler),
@@ -359,9 +355,6 @@ public void Free() {
Marshal.FreeCoTaskMem(_unmanaged.TemporaryFilesPath);
Marshal.FreeCoTaskMem(_unmanaged.UserAgent);
Marshal.FreeCoTaskMem(_unmanaged.BrowserControlInitParameters);
- Marshal.FreeCoTaskMem(_unmanaged.WebView2RuntimePath);
- Marshal.FreeCoTaskMem(_unmanaged.NotificationRegistrationId);
- Marshal.FreeCoTaskMem(_unmanaged.WindowsAppUserModelId);
Marshal.FreeCoTaskMem(_unmanaged.DefaultNotificationIcon);
Marshal.FreeCoTaskMem(_unmanaged.MenuBarJson);
diff --git a/src/InfiniFrame.NativeBridge/Managed/Parameters/InfiniFrameNativeParametersValidator.cs b/src/InfiniFrame.NativeBridge/Managed/Parameters/Window/InfiniFrameNativeWindowParametersValidator.cs
similarity index 89%
rename from src/InfiniFrame.NativeBridge/Managed/Parameters/InfiniFrameNativeParametersValidator.cs
rename to src/InfiniFrame.NativeBridge/Managed/Parameters/Window/InfiniFrameNativeWindowParametersValidator.cs
index 5eeacce35..8efa85ae0 100644
--- a/src/InfiniFrame.NativeBridge/Managed/Parameters/InfiniFrameNativeParametersValidator.cs
+++ b/src/InfiniFrame.NativeBridge/Managed/Parameters/Window/InfiniFrameNativeWindowParametersValidator.cs
@@ -4,26 +4,26 @@
using System.Runtime.InteropServices;
using FluentValidation;
-namespace InfiniFrame.NativeBridge.Parameters;
+namespace InfiniFrame.NativeBridge.Parameters.Window;
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
///
-/// Validates instances using FluentValidation rules.
+/// Validates instances using FluentValidation rules.
///
-public sealed class InfiniFrameNativeParametersValidator
- : AbstractValidator {
+public sealed class InfiniFrameNativeWindowParametersValidator
+ : AbstractValidator {
// -----------------------------------------------------------------------------------------------------------------
// Constructors
// -----------------------------------------------------------------------------------------------------------------
///
- /// Initializes a new instance of
+/// Initializes a new instance of
/// and configures all validation rules.
///
- public InfiniFrameNativeParametersValidator() {
+ public InfiniFrameNativeWindowParametersValidator() {
RuleFor(p => p.Size)
- .Equal(Marshal.SizeOf());
+ .Equal(Marshal.SizeOf());
RuleFor(p => p)
.Must(p =>
@@ -87,12 +87,6 @@ public InfiniFrameNativeParametersValidator() {
.NotNull().WithMessage("CustomSchemeNames must be specified.")
.Must(names => names.Length <= 16).WithMessage("CustomSchemeNames must contain at most 16 names.");
- RuleFor(p => p.WindowsAppUserModelId)
- .NotEmpty()
- .MaximumLength(128)
- .Must(value => value is null || !value.Any(char.IsWhiteSpace))
- .When(p => p.WindowsAppUserModelId is not null)
- .WithMessage("WindowsAppUserModelId must contain 1 to 128 characters and cannot contain whitespace.");
}
// -----------------------------------------------------------------------------------------------------------------
diff --git a/src/InfiniFrame.Shared/Utilities/UnixPInvoke.cs b/src/InfiniFrame.NativeBridge/Managed/UnixPInvoke.cs
similarity index 84%
rename from src/InfiniFrame.Shared/Utilities/UnixPInvoke.cs
rename to src/InfiniFrame.NativeBridge/Managed/UnixPInvoke.cs
index cf991d63d..c438aebeb 100644
--- a/src/InfiniFrame.Shared/Utilities/UnixPInvoke.cs
+++ b/src/InfiniFrame.NativeBridge/Managed/UnixPInvoke.cs
@@ -1,9 +1,9 @@
-// ---------------------------------------------------------------------------------------------------------------------
+// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
using System.Runtime.InteropServices;
-namespace InfiniFrame.Utilities;
+namespace InfiniFrame.NativeBridge;
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
diff --git a/src/InfiniFrame.NativeBridge/Native/CMakeLists.txt b/src/InfiniFrame.NativeBridge/Native/CMakeLists.txt
index 6ab5b1c82..99436664c 100644
--- a/src/InfiniFrame.NativeBridge/Native/CMakeLists.txt
+++ b/src/InfiniFrame.NativeBridge/Native/CMakeLists.txt
@@ -40,32 +40,34 @@ infiniframe_setup_dependencies()
# ----------------------------------------------------------------------------------------------------------------------
set(COMMON_SOURCES
src/Embedded/Embedded.cpp
- src/Api/Utilities/ExportErrorState.cpp
- src/Runtime/Shared/Window/WindowEvents.cpp
- src/Runtime/Shared/Window/WindowState.cpp
- src/Runtime/Shared/Operations/NativeOperation.cpp
- src/Runtime/Shared/Operations/DialogOperation.cpp
- src/Api/Exports/Exports.CustomSchemes.cpp
- src/Api/Exports/Exports.Dispatch.cpp
- src/Api/Exports/Exports.Dialog.cpp
- src/Api/Exports/Exports.Events.cpp
- src/Api/Exports/Exports.Lifecycle.cpp
- src/Api/Exports/Exports.Memory.cpp
- src/Api/Exports/Exports.Monitors.cpp
- src/Api/Exports/Exports.Platform.Linux.cpp
- src/Api/Exports/Exports.Platform.MacOs.cpp
- src/Api/Exports/Exports.Platform.Windows.cpp
- src/Api/Exports/Exports.Window.Actions.cpp
- src/Api/Exports/Exports.Window.Getters.cpp
- src/Api/Exports/Exports.Window.Navigation.cpp
- src/Api/Exports/Exports.Window.Setters.cpp
- src/Api/Exports/Exports.Window.Taskbar.cpp
- src/Api/Exports/Exports.Menu.cpp
+ src/Runtime/Internal/Interop/Exports/ExportErrorState.cpp
+ src/Runtime/Internal/Application/InfiniFrameApplication.cpp
+ src/Runtime/Internal/Window/WindowEvents.cpp
+ src/Runtime/Internal/Window/WindowState.cpp
+ src/Runtime/Internal/Operations/NativeOperation.cpp
+ src/Runtime/Internal/Operations/DialogOperation.cpp
+ src/Abi/Exports.CustomSchemes.cpp
+ src/Abi/Exports.Dispatch.cpp
+ src/Abi/Exports.Dialog.cpp
+ src/Abi/Exports.Events.cpp
+ src/Abi/Exports.Lifecycle.cpp
+ src/Abi/Exports.Memory.cpp
+ src/Abi/Exports.Monitors.cpp
+ src/Abi/Exports.Platform.Linux.cpp
+ src/Abi/Exports.Platform.MacOs.cpp
+ src/Abi/Exports.Platform.Windows.cpp
+ src/Abi/Exports.Window.Actions.cpp
+ src/Abi/Exports.Window.Getters.cpp
+ src/Abi/Exports.Window.Navigation.cpp
+ src/Abi/Exports.Window.Setters.cpp
+ src/Abi/Exports.Window.Taskbar.cpp
+ src/Abi/Exports.Menu.cpp
)
set(TEST_SOURCES
- src/Api/Testing/Exports.Tests.cpp
- src/Api/Testing/Exports.CustomSchemeResponseTests.cpp
+ src/Abi/Testing/Exports.Tests.cpp
+ src/Abi/Testing/Exports.CustomSchemeResponseTests.cpp
+ tests/PublicAbiCompileBoundary.cpp
)
set(WINDOWS_SOURCES
@@ -163,30 +165,31 @@ set(MAC_SOURCES
set(HEADER_FILES
src/Embedded/Embedded.h
- src/Runtime/Shared/WebView/CustomSchemeResponse.h
- src/Runtime/Shared/Operations/NativeOperation.h
- src/Runtime/Shared/Operations/NavigationOperation.h
- src/Api/Exports/Exports.h
- src/Api/Utilities/ExportErrorState.h
- src/Api/Utilities/ExportExecution.h
- src/Api/Utilities/ExportStringHelpers.h
- src/Api/Utilities/ExportValidation.h
- src/Api/Utilities/Utilities.h
- src/Runtime/Shared/Window/InfiniFrame.h
- src/Runtime/Shared/Window/InfiniFrameDialog.h
- src/Runtime/Shared/Window/InfiniFrameInitParams.h
- src/Runtime/Shared/Window/InfiniFrameWindow.h
- src/Runtime/Shared/Types/Basic.h
- src/Runtime/Shared/Types/Callbacks.h
- src/Runtime/Shared/Types/DialogButtons.h
- src/Runtime/Shared/Types/DialogIcon.h
- src/Runtime/Shared/Types/DialogResult.h
- src/Runtime/Shared/Types/Monitor.h
- src/Runtime/Shared/Utilities/Dimensions.h
- src/Runtime/Shared/Utilities/ErrorCode.h
- src/Runtime/Shared/Utilities/InteropStatus.h
- src/Runtime/Shared/Utilities/StringArrayCopy.h
- src/Runtime/Shared/Utilities/StringCopy.h
+ src/Runtime/Internal/WebView/CustomSchemeResponse.h
+ src/Runtime/Internal/Operations/NativeOperation.h
+ src/Runtime/Internal/Operations/NavigationOperation.h
+ src/Abi/Exports.h
+ src/Runtime/Internal/Interop/Exports/ExportErrorState.h
+ src/Runtime/Internal/Interop/Exports/ExportExecution.h
+ src/Runtime/Internal/Interop/Exports/ExportStringHelpers.h
+ src/Runtime/Internal/Interop/Exports/ExportValidation.h
+ src/Runtime/Internal/Interop/Types/InfiniFrameApplicationInitParams.h
+ src/Runtime/Internal/Interop/Types/InteropStatus.h
+ src/Runtime/Internal/Window/InfiniFrameDialog.h
+ src/Runtime/Internal/Interop/Types/InfiniFrameWindowInitParams.h
+ src/Runtime/Internal/Interop/Types/InfiniFrameWindow.h
+ src/Runtime/Internal/Application/InfiniFrameApplication.h
+ src/Runtime/Internal/Interop/Types/Basic.h
+ src/Runtime/Internal/Interop/Types/Callbacks.h
+ src/Runtime/Internal/Interop/Types/CustomSchemeResponse.h
+ src/Runtime/Internal/Interop/Types/DialogButtons.h
+ src/Runtime/Internal/Interop/Types/DialogIcon.h
+ src/Runtime/Internal/Interop/Types/DialogResult.h
+ src/Runtime/Internal/Interop/Types/Monitor.h
+ src/Runtime/Internal/Utilities/Dimensions.h
+ src/Runtime/Internal/Utilities/ErrorCode.h
+ src/Runtime/Internal/Utilities/StringArrayCopy.h
+ src/Runtime/Internal/Utilities/StringCopy.h
)
if (WIN32)
diff --git a/src/InfiniFrame.NativeBridge/Native/README.md b/src/InfiniFrame.NativeBridge/Native/README.md
index 9fabf5ef2..294e1d7ad 100644
--- a/src/InfiniFrame.NativeBridge/Native/README.md
+++ b/src/InfiniFrame.NativeBridge/Native/README.md
@@ -10,8 +10,7 @@ Native/
BUILDING.md # Build performance tips
src/
- Api/
- Exports/ # extern "C" functions (the public C ABI)
+ Abi/ # extern "C" functions (the public C ABI)
Exports.Window.Actions.cpp # Center, Restore, Focus, Notifications
Exports.Window.Getters.cpp # Query window state (size, position, flags)
Exports.Window.Setters.cpp # Modify window state
@@ -28,30 +27,15 @@ Native/
Exports.Platform.Windows.cpp # Windows-specific exports
Exports.Platform.MacOs.cpp # macOS-specific exports
Exports.Platform.Linux.cpp # Linux-specific exports
- Testing/ # Test-only exports
- Utilities/ # Export infrastructure (validation, error state, string helpers)
-
- Runtime/
- Shared/ # Cross-platform runtime code
- Window/ # Window state, events, configuration
- Operations/ # Async operation infrastructure
- Platform/ # Platform detection
+ Runtime/Internal/ # Internal runtime, interop types, operations, and window state
+ Runtime/Platform/ # Windows, Linux, and macOS implementations
Embedded/ # Embedded JS assets
-
- Platforms/
- Windows/ # WebView2 implementation
- Linux/ # WebKitGTK implementation
- MacOs/ # WKWebView implementation
-
- include/
- InfiniFrameWindow.h # Main public header InfiniFrameWindow class
- Types/ # Shared ABI types (enums, structs)
```
## Public C API
-The public API is defined in `src/Api/Exports/` and consists of `extern "C"` functions with the prefix `InfiniFrameNative_`. These functions are called by the .NET managed layer via P/Invoke.
+The public API is defined in `src/Abi/` and consists of `extern "C"` functions with the prefix `InfiniFrameNative_`. These functions are called by the .NET managed layer via P/Invoke.
### String Ownership
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.CustomSchemes.cpp b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.CustomSchemes.cpp
similarity index 96%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.CustomSchemes.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.CustomSchemes.cpp
index d5c91a815..61099f026 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.CustomSchemes.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.CustomSchemes.cpp
@@ -1,7 +1,7 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#include "Api/Exports/Exports.h"
+#include "Abi/Exports.h"
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
@@ -19,4 +19,4 @@ EXPORTED InteropStatus InfiniFrameNative_AddCustomSchemeName(InfiniFrameWindow*
window->AddCustomSchemeName(scheme);
});
}
-}
\ No newline at end of file
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Dialog.cpp b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Dialog.cpp
similarity index 99%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Dialog.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Dialog.cpp
index 10cfa862c..20d072af9 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Dialog.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Dialog.cpp
@@ -1,7 +1,8 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#include "Api/Exports/Exports.h"
+#include "Abi/Exports.h"
+#include "Runtime/Internal/Window/InfiniFrameDialog.h"
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
@@ -274,4 +275,4 @@ InfiniFrameNative_CancelDialog(InfiniFrameWindow* instance, const uint64_t opera
*cancelled = window->CancelDialog(operationId);
});
}
-}
\ No newline at end of file
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Dispatch.cpp b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Dispatch.cpp
similarity index 97%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Dispatch.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Dispatch.cpp
index cfcacfa2e..4cabbe803 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Dispatch.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Dispatch.cpp
@@ -1,8 +1,8 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#include "Api/Exports/Exports.h"
-#include "Runtime/Shared/Operations/NativeOperation.h"
+#include "Abi/Exports.h"
+#include "Runtime/Internal/Operations/NativeOperation.h"
#ifdef __linux__
#include "Runtime/Platform/Linux/Core/UiThread.Gtk.h"
#endif
@@ -87,4 +87,4 @@ EXPORTED InteropStatus InfiniFrameNative_CancelOperation(
window->CancelOperation(operationId, static_cast(result));
});
}
-}
\ No newline at end of file
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Events.cpp b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Events.cpp
similarity index 99%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Events.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Events.cpp
index 02cdbd338..4d3088c88 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Events.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Events.cpp
@@ -1,7 +1,7 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#include "Api/Exports/Exports.h"
+#include "Abi/Exports.h"
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
@@ -103,4 +103,4 @@ EXPORTED InteropStatus InfiniFrameNative_SetDragDropEnabled(InfiniFrameWindow* i
window->SetDragDropEnabled(enabled != 0);
});
}
-}
\ No newline at end of file
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Lifecycle.cpp b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Lifecycle.cpp
similarity index 51%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Lifecycle.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Lifecycle.cpp
index 73b6c49e6..e23262c0b 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Lifecycle.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Lifecycle.cpp
@@ -1,7 +1,9 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#include "Api/Exports/Exports.h"
+#include "Abi/Exports.h"
+#include "Runtime/Internal/Interop/Types/InfiniFrameWindowInitParams.h"
+#include "Runtime/Internal/Application/InfiniFrameApplication.h"
#ifdef __linux__
#include "Runtime/Platform/Linux/Core/UiThread.Gtk.h"
#endif
@@ -9,11 +11,93 @@
// Code
// ---------------------------------------------------------------------------------------------------------------------
extern "C" {
+/// @brief Creates a native application instance.
+EXPORTED InteropStatus InfiniFrameNativeApplication_ctor(InfiniFrameApplication** value) {
+ ResetOut(value, static_cast(nullptr));
+ return RunExportStatus(
+ [&] {
+ if (!EnsureOutNotNull(value, "value")) return;
+ auto instance = std::make_unique();
+ *value = instance.release();
+ });
+}
+
+/// @brief Registers process-wide native application state.
+EXPORTED InteropStatus InfiniFrameNativeApplication_Register(InfiniFrameApplication* instance) {
+ return RunExportStatus(
+ [&] {
+ if (!EnsureNotNull(instance, "instance")) return;
+ instance->Register();
+ });
+}
+
+EXPORTED InteropStatus InfiniFrameNativeApplication_Configure(
+ InfiniFrameApplication* instance,
+ InfiniFrameApplicationInitParams* parameters
+) {
+ return RunExportStatus(
+ [&] {
+ if (!EnsureNotNull(instance, "instance")) return;
+ if (!EnsureNotNull(parameters, "parameters")) return;
+ if (parameters->StructSize != static_cast(sizeof(InfiniFrameApplicationInitParams)))
+ throw std::invalid_argument("InfiniFrameApplicationInitParams.Size does not match native struct size.");
+ instance->Configure(*parameters);
+ });
+}
+
+/// @brief Runs the native application loop.
+EXPORTED InteropStatus InfiniFrameNativeApplication_Run(InfiniFrameApplication* instance) {
+ return RunExportStatus(
+ [&] {
+ if (!EnsureNotNull(instance, "instance")) return;
+ instance->Run();
+ });
+}
+
+/// @brief Requests native application shutdown.
+EXPORTED InteropStatus InfiniFrameNativeApplication_Shutdown(InfiniFrameApplication* instance) {
+ return RunExportStatus(
+ [&] {
+ if (!EnsureNotNull(instance, "instance")) return;
+ instance->Shutdown();
+ });
+}
+
+/// @brief Destroys a native application instance.
+EXPORTED InteropStatus InfiniFrameNativeApplication_dtor(InfiniFrameApplication* instance) {
+ return RunExportStatus(
+ [&] {
+ if (!EnsureNotNull(instance, "instance")) return;
+#ifdef __APPLE__
+ // macOS SafeHandle release is intentionally asynchronous. Keep the
+ // application owner alive until deferred native window deletion has
+ // unregistered every C++ window object.
+ instance->WaitForWindowDestruction();
+#endif
+ if (instance->GetWindowCount() != 0)
+ throw std::runtime_error("Cannot destroy a native application while windows are still alive.");
+ std::unique_ptr guard{instance};
+ });
+}
+
+/// @brief Returns the number of native windows tracked by the application.
+EXPORTED InteropStatus InfiniFrameNativeApplication_GetWindowCount(
+ InfiniFrameApplication* instance,
+ std::size_t* value
+) {
+ ResetOut(value, static_cast(0));
+ return RunExportStatus(
+ [&] {
+ if (!EnsureNotNull(instance, "instance") || !EnsureOutNotNull(value, "value")) return;
+ *value = instance->GetWindowCount();
+ });
+}
+
/// @brief Creates a new native window with the given parameters.
/// @param initParams Initialization parameters for the window.
/// @param[out] value Receives the newly created window handle.
/// @return InteropStatus
-EXPORTED InteropStatus InfiniFrameNative_ctor(InfiniFrameInitParams* initParams, InfiniFrameWindow** value) {
+EXPORTED InteropStatus InfiniFrameNative_ctor(InfiniFrameWindowInitParams* initParams, InfiniFrameWindow** value) {
ResetOut(value, static_cast(nullptr));
return RunExportStatus(
[&] {
@@ -21,8 +105,8 @@ EXPORTED InteropStatus InfiniFrameNative_ctor(InfiniFrameInitParams* initParams,
return;
if (initParams == nullptr)
throw std::invalid_argument("Argument 'initParams' is null.");
- if (initParams->StructSize != static_cast(sizeof(InfiniFrameInitParams))) {
- throw std::invalid_argument("InfiniFrameInitParams.Size does not match native struct size.");
+ if (initParams->StructSize != static_cast(sizeof(InfiniFrameWindowInitParams))) {
+ throw std::invalid_argument("InfiniFrameWindowInitParams.Size does not match native struct size.");
}
auto instance = std::make_unique(initParams);
*value = instance.release();
@@ -86,6 +170,19 @@ EXPORTED InteropStatus InfiniFrameNative_SetReadyCallback(
});
}
+EXPORTED InteropStatus InfiniFrameNative_SetReadyFailureCallback(
+ InfiniFrameWindow* instance,
+ const ContextAction callback,
+ void* context
+ ) {
+ return RunWindowExportStatus(
+ instance, [&](InfiniFrameWindow* window) {
+ if (callback == nullptr)
+ throw std::invalid_argument("Argument 'callback' is null.");
+ window->SetReadyFailureCallback(callback, context);
+ });
+}
+
/// @brief Registers a callback for when teardown begins.
/// @param instance The window handle.
/// @param callback Context action invoked when teardown starts.
@@ -105,7 +202,7 @@ EXPORTED InteropStatus InfiniFrameNative_SetTeardownCallback(
}
#ifdef __linux__
-/// @brief Forces immediate shutdown of the native window (Linux only).
+/// @brief Shuts down the Linux UI thread (legacy process-level entry point).
/// @return InteropStatus
EXPORTED InteropStatus InfiniFrameNative_Shutdown() {
return RunExportStatus(
@@ -114,4 +211,4 @@ EXPORTED InteropStatus InfiniFrameNative_Shutdown() {
});
}
#endif
-}
\ No newline at end of file
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Memory.cpp b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Memory.cpp
similarity index 98%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Memory.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Memory.cpp
index 273f8e961..6c4bcf844 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Memory.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Memory.cpp
@@ -1,7 +1,7 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#include "Api/Exports/Exports.h"
+#include "Abi/Exports.h"
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
@@ -51,4 +51,4 @@ EXPORTED InteropStatus InfiniFrameNative_GetLastErrorMessage(const char** value)
*value = GetLastErrorMessageCopy();
return InteropStatus::Success;
}
-}
\ No newline at end of file
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Menu.cpp b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Menu.cpp
similarity index 98%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Menu.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Menu.cpp
index 9bfcc3d62..8cdf5886b 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Menu.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Menu.cpp
@@ -1,7 +1,7 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#include "Api/Exports/Exports.h"
+#include "Abi/Exports.h"
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
@@ -57,4 +57,4 @@ EXPORTED InteropStatus InfiniFrameNative_ClickMenuItem(InfiniFrameWindow* instan
window->ClickMenuItemById(menuItemId);
});
}
-}
\ No newline at end of file
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Monitors.cpp b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Monitors.cpp
similarity index 97%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Monitors.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Monitors.cpp
index 11a4c2525..bf3579900 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Monitors.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Monitors.cpp
@@ -1,7 +1,7 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#include "Api/Exports/Exports.h"
+#include "Abi/Exports.h"
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
@@ -20,4 +20,4 @@ EXPORTED InteropStatus InfiniFrameNative_GetAllMonitors(
window->GetAllMonitors(callback);
});
}
-}
\ No newline at end of file
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Platform.Linux.cpp b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Platform.Linux.cpp
similarity index 96%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Platform.Linux.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Platform.Linux.cpp
index 5e36b5b19..fe4c74b75 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Platform.Linux.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Platform.Linux.cpp
@@ -1,7 +1,7 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#include "Api/Exports/Exports.h"
+#include "Abi/Exports.h"
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
@@ -21,4 +21,4 @@ EXPORTED InteropStatus InfiniFrameNative_getGtkWindow_linux(InfiniFrameWindow* i
});
}
#endif
-}
\ No newline at end of file
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Platform.MacOs.cpp b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Platform.MacOs.cpp
similarity index 97%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Platform.MacOs.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Platform.MacOs.cpp
index 885b695fe..5b392ba02 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Platform.MacOs.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Platform.MacOs.cpp
@@ -1,7 +1,7 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#include "Api/Exports/Exports.h"
+#include "Abi/Exports.h"
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
@@ -31,4 +31,4 @@ EXPORTED InteropStatus InfiniFrameNative_getNSWindow_mac(InfiniFrameWindow* inst
});
}
#endif
-}
\ No newline at end of file
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Platform.Windows.cpp b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Platform.Windows.cpp
similarity index 99%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Platform.Windows.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Platform.Windows.cpp
index b0dfc3b6c..182861895 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Platform.Windows.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Platform.Windows.cpp
@@ -1,7 +1,7 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#include "Api/Exports/Exports.h"
+#include "Abi/Exports.h"
#ifdef _WIN32
#include "Runtime/Platform/Windows/Window.Win32.Context.h"
#endif
@@ -85,4 +85,4 @@ EXPORTED InteropStatus InfiniFrameNative_getWebView2RuntimeVersion_win32(const c
});
}
#endif
-}
\ No newline at end of file
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Window.Actions.cpp b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Window.Actions.cpp
similarity index 99%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Window.Actions.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Window.Actions.cpp
index 379dd47f0..d791cb8ff 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Window.Actions.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Window.Actions.cpp
@@ -1,7 +1,7 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#include "Api/Exports/Exports.h"
+#include "Abi/Exports.h"
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
@@ -133,4 +133,4 @@ EXPORTED InteropStatus InfiniFrameNative_CancelNotification(
window->CancelNotification(operationId, canceled);
});
}
-}
\ No newline at end of file
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Window.Getters.cpp b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Window.Getters.cpp
similarity index 99%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Window.Getters.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Window.Getters.cpp
index 395f695e1..e1d426806 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Window.Getters.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Window.Getters.cpp
@@ -1,7 +1,7 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#include "Api/Exports/Exports.h"
+#include "Abi/Exports.h"
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
@@ -445,4 +445,4 @@ EXPORTED InteropStatus InfiniFrameNative_GetIconFileName(InfiniFrameWindow* inst
*value = window->GetIconFileName();
});
}
-}
\ No newline at end of file
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Window.Navigation.cpp b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Window.Navigation.cpp
similarity index 99%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Window.Navigation.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Window.Navigation.cpp
index 80c5765a9..9cb23788c 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Window.Navigation.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Window.Navigation.cpp
@@ -1,7 +1,7 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#include "Api/Exports/Exports.h"
+#include "Abi/Exports.h"
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
@@ -113,4 +113,4 @@ EXPORTED InteropStatus InfiniFrameNative_GetCurrentUrl(InfiniFrameWindow* instan
*value = window->GetCurrentUrl();
});
}
-}
\ No newline at end of file
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Window.Setters.cpp b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Window.Setters.cpp
similarity index 99%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Window.Setters.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Window.Setters.cpp
index 7dde8b3d7..569f5ce55 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Window.Setters.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Window.Setters.cpp
@@ -1,7 +1,7 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#include "Api/Exports/Exports.h"
+#include "Abi/Exports.h"
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
@@ -248,4 +248,4 @@ EXPORTED InteropStatus InfiniFrameNative_SetZoom(InfiniFrameWindow* instance, co
window->SetZoom(zoom);
});
}
-}
\ No newline at end of file
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Window.Taskbar.cpp b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Window.Taskbar.cpp
similarity index 98%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Window.Taskbar.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Window.Taskbar.cpp
index c01f6ce6e..3ebc50bf2 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Window.Taskbar.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Window.Taskbar.cpp
@@ -1,7 +1,7 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#include "Api/Exports/Exports.h"
+#include "Abi/Exports.h"
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
@@ -76,4 +76,4 @@ EXPORTED InteropStatus InfiniFrameNative_GetTaskbarProgressSupported(
window->GetTaskbarProgressSupported(supported);
});
}
-}
\ No newline at end of file
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.h b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.h
similarity index 64%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.h
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.h
index f274d916b..3b5ae0a3e 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.h
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.h
@@ -14,9 +14,24 @@
#define EXPORTED
#endif
-#include "Runtime/Shared/Types/Basic.h"
-#include "Runtime/Shared/Utilities/InteropStatus.h"
-#include "Api/Utilities/Utilities.h"
+#include "Runtime/Internal/Interop/Types/Basic.h"
+#include "Runtime/Internal/Interop/Types/InteropStatus.h"
+#include "Runtime/Internal/Interop/Exports/ExportErrorState.h"
+#include "Runtime/Internal/Interop/Exports/ExportExecution.h"
+#include "Runtime/Internal/Interop/Exports/ExportStringHelpers.h"
+#include "Runtime/Internal/Interop/Exports/ExportValidation.h"
+
+using infiniframe::exports::EnsureNotNull;
+using infiniframe::exports::EnsureOutNotNull;
+using infiniframe::exports::GetLastErrorMessageCopy;
+using infiniframe::exports::ResetOut;
+using infiniframe::exports::ResetOut2;
+using infiniframe::exports::RunExportStatus;
+using infiniframe::exports::RunReturnExport;
+using infiniframe::exports::RunWindowExportStatus;
+using infiniframe::exports::RunWindowReturnExport;
+using infiniframe::exports::NullToEmpty;
+using infiniframe::exports::DuplicateString;
// ---------------------------------------------------------------------------------------------------------------------
// String Ownership Contract
@@ -36,4 +51,4 @@
//
// NULL semantics:
// Returning nullptr from an owned-string function means "no value" (e.g. no
-// file selected). The caller must still check before calling FreeString.
\ No newline at end of file
+// file selected). The caller must still check before calling FreeString.
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Testing/Exports.CustomSchemeResponseTests.cpp b/src/InfiniFrame.NativeBridge/Native/src/Abi/Testing/Exports.CustomSchemeResponseTests.cpp
similarity index 96%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Testing/Exports.CustomSchemeResponseTests.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Testing/Exports.CustomSchemeResponseTests.cpp
index 1bee06f24..9490b4084 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Testing/Exports.CustomSchemeResponseTests.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Testing/Exports.CustomSchemeResponseTests.cpp
@@ -1,9 +1,9 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#include "Runtime/Shared/Window/InfiniFrame.h"
-#include "Api/Exports/Exports.h"
-#include "Runtime/Shared/WebView/CustomSchemeResponse.h"
+#include "Abi/Exports.h"
+#include "Runtime/Internal/WebView/CustomSchemeResponse.h"
+#include "Runtime/Internal/Utilities/StringCopy.h"
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
@@ -116,4 +116,4 @@ EXPORTED InteropStatus InfiniFrameNativeTests_BuildHeaders(
}
}
-#endif
\ No newline at end of file
+#endif
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Testing/Exports.Tests.cpp b/src/InfiniFrame.NativeBridge/Native/src/Abi/Testing/Exports.Tests.cpp
similarity index 75%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Testing/Exports.Tests.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Testing/Exports.Tests.cpp
index 064d30c9f..8063e7dc4 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Testing/Exports.Tests.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Testing/Exports.Tests.cpp
@@ -1,15 +1,19 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#include "Runtime/Shared/Window/InfiniFrame.h"
-#include "Api/Exports/Exports.h"
-#include "Runtime/Shared/WebView/CustomSchemeResponse.h"
+#include "Runtime/Internal/Interop/Types/InfiniFrameWindowInitParams.h"
+#include "Runtime/Internal/Interop/Types/InfiniFrameApplicationInitParams.h"
+#include "Abi/Exports.h"
+#include "Runtime/Internal/WebView/CustomSchemeResponse.h"
#ifdef _WIN32
#include "Runtime/Platform/Windows/DarkMode.h"
#endif
#ifdef __APPLE__
#include "Runtime/Platform/Mac/Window.Cocoa.Internal.h"
#endif
+#ifdef __linux__
+#include "Runtime/Platform/Linux/Core/UiThread.Gtk.h"
+#endif
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
@@ -28,8 +32,8 @@ EXPORTED InteropStatus InfiniFrameNativeTests_MacPooledHostCount(size_t* value)
}
#endif
EXPORTED InteropStatus InfiniFrameNativeTests_NativeParametersReturnAsIs(
- const InfiniFrameInitParams* params,
- InfiniFrameInitParams** new_params
+ const InfiniFrameWindowInitParams* params,
+ InfiniFrameWindowInitParams** new_params
) {
if (new_params != nullptr) {
*new_params = nullptr;
@@ -42,7 +46,7 @@ EXPORTED InteropStatus InfiniFrameNativeTests_NativeParametersReturnAsIs(
return;
}
- *new_params = new InfiniFrameInitParams();
+ *new_params = new InfiniFrameWindowInitParams();
// Content strings
(*new_params)->StartString = DuplicateString(params->StartString);
@@ -54,9 +58,6 @@ EXPORTED InteropStatus InfiniFrameNativeTests_NativeParametersReturnAsIs(
(*new_params)->TemporaryFilesPath = DuplicateString(params->TemporaryFilesPath);
(*new_params)->UserAgent = DuplicateString(params->UserAgent);
(*new_params)->BrowserControlInitParameters = DuplicateString(params->BrowserControlInitParameters);
- (*new_params)->WebView2RuntimePath = DuplicateString(params->WebView2RuntimePath);
- (*new_params)->NotificationRegistrationId = DuplicateString(params->NotificationRegistrationId);
- (*new_params)->WindowsAppUserModelId = DuplicateString(params->WindowsAppUserModelId);
(*new_params)->DefaultNotificationIcon = DuplicateString(params->DefaultNotificationIcon);
// Runtime configuration
@@ -64,6 +65,7 @@ EXPORTED InteropStatus InfiniFrameNativeTests_NativeParametersReturnAsIs(
// Parent window
(*new_params)->ParentInstance = params->ParentInstance;
+ (*new_params)->ApplicationInstance = params->ApplicationInstance;
// Event callbacks
(*new_params)->ClosingHandler = params->ClosingHandler;
@@ -79,7 +81,7 @@ EXPORTED InteropStatus InfiniFrameNativeTests_NativeParametersReturnAsIs(
(*new_params)->DebugEventHandler = params->DebugEventHandler;
// Custom scheme support
- for (size_t i = 0; i < InfiniFrameInitParams::MaxCustomSchemeNames; ++i) {
+ for (size_t i = 0; i < InfiniFrameWindowInitParams::MaxCustomSchemeNames; ++i) {
(*new_params)->CustomSchemeNames[i] = params->CustomSchemeNames[i] != nullptr
? DuplicateString(params->CustomSchemeNames[i])
: nullptr;
@@ -143,7 +145,7 @@ EXPORTED InteropStatus InfiniFrameNativeTests_NativeParametersReturnAsIs(
});
}
-EXPORTED InteropStatus InfiniFrameNativeTests_FreeInitParams(InfiniFrameInitParams* params) {
+EXPORTED InteropStatus InfiniFrameNativeTests_FreeInitParams(InfiniFrameWindowInitParams* params) {
return RunExportStatus(
[&] {
if (!EnsureNotNull(params, "params")) {
@@ -158,11 +160,8 @@ EXPORTED InteropStatus InfiniFrameNativeTests_FreeInitParams(InfiniFrameInitPara
delete[] params->TemporaryFilesPath;
delete[] params->UserAgent;
delete[] params->BrowserControlInitParameters;
- delete[] params->WebView2RuntimePath;
- delete[] params->NotificationRegistrationId;
- delete[] params->WindowsAppUserModelId;
delete[] params->DefaultNotificationIcon;
- for (size_t i = 0; i < InfiniFrameInitParams::MaxCustomSchemeNames; ++i) {
+ for (size_t i = 0; i < InfiniFrameWindowInitParams::MaxCustomSchemeNames; ++i) {
delete[] params->CustomSchemeNames[i];
}
delete[] params->MenuBarJson;
@@ -171,6 +170,41 @@ EXPORTED InteropStatus InfiniFrameNativeTests_FreeInitParams(InfiniFrameInitPara
});
}
+EXPORTED InteropStatus InfiniFrameNativeTests_NativeApplicationParametersReturnAsIs(
+ const InfiniFrameApplicationInitParams* params,
+ InfiniFrameApplicationInitParams** new_params
+ ) {
+ if (new_params != nullptr) *new_params = nullptr;
+
+ return RunExportStatus(
+ [&] {
+ if (!EnsureNotNull(params, "params") ||
+ !EnsureNotNull(new_params, "new_params", ::InteropStatus::OutParameterSetToInvalidNull)) {
+ return;
+ }
+
+ *new_params = new InfiniFrameApplicationInitParams {
+ DuplicateString(params->WebView2RuntimePath),
+ DuplicateString(params->NotificationRegistrationId),
+ DuplicateString(params->AppUserModelId),
+ DuplicateString(params->DefaultNotificationIcon),
+ params->StructSize
+ };
+ });
+}
+
+EXPORTED InteropStatus InfiniFrameNativeTests_FreeApplicationParameters(InfiniFrameApplicationInitParams* params) {
+ return RunExportStatus(
+ [&] {
+ if (!EnsureNotNull(params, "params")) return;
+ delete[] params->WebView2RuntimePath;
+ delete[] params->NotificationRegistrationId;
+ delete[] params->AppUserModelId;
+ delete[] params->DefaultNotificationIcon;
+ delete params;
+ });
+}
+
EXPORTED InteropStatus InfiniFrameNativeTests_ConsumeCustomSchemeResponse(
void* callbackPointer,
uint64_t* contentLength,
@@ -210,6 +244,36 @@ EXPORTED InteropStatus InfiniFrameNativeTests_ConsumeCustomSchemeResponse(
});
}
+#ifdef __linux__
+EXPORTED InteropStatus InfiniFrameNativeTests_GtkUiThreadLifecycle(int* result) {
+ if (result != nullptr) {
+ *result = 0;
+ }
+
+ return RunExportStatus(
+ [&] {
+ if (!EnsureNotNull(result, "result", ::InteropStatus::OutParameterSetToInvalidNull)) {
+ return;
+ }
+
+ // Exercise repeated init/shutdown cycles to verify:
+ // - unique_ptr is properly reset (no leak / no use-after-free)
+ // - std::atexit is registered at most once
+ // - re-initialization after shutdown succeeds
+ for (int i = 0; i < 2; ++i) {
+ infiniframe::linux_gtk::ui_thread::EnsureInitialized();
+ infiniframe::linux_gtk::ui_thread::Shutdown();
+ }
+
+ // Final init so the process can exit cleanly through the atexit path.
+ infiniframe::linux_gtk::ui_thread::EnsureInitialized();
+ infiniframe::linux_gtk::ui_thread::RequestShutdown();
+
+ *result = 1;
+ });
+}
+#endif
+
#ifdef _WIN32
EXPORTED InteropStatus InfiniFrameNativeTests_IsColorSchemeChange(const LPARAM lParam, int* result) {
if (result != nullptr) {
@@ -228,4 +292,4 @@ EXPORTED InteropStatus InfiniFrameNativeTests_IsColorSchemeChange(const LPARAM l
#endif
}
-#endif
\ No newline at end of file
+#endif
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Utilities/Utilities.h b/src/InfiniFrame.NativeBridge/Native/src/Api/Utilities/Utilities.h
deleted file mode 100644
index 5e5c92069..000000000
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Utilities/Utilities.h
+++ /dev/null
@@ -1,23 +0,0 @@
-#pragma once
-// ---------------------------------------------------------------------------------------------------------------------
-// Imports
-// ---------------------------------------------------------------------------------------------------------------------
-#include "ExportErrorState.h"
-#include "ExportExecution.h"
-#include "ExportStringHelpers.h"
-#include "ExportValidation.h"
-
-// ---------------------------------------------------------------------------------------------------------------------
-// Code
-// ---------------------------------------------------------------------------------------------------------------------
-using infiniframe::exports::EnsureNotNull;
-using infiniframe::exports::EnsureOutNotNull;
-using infiniframe::exports::GetLastErrorMessageCopy;
-using infiniframe::exports::ResetOut;
-using infiniframe::exports::ResetOut2;
-using infiniframe::exports::RunExportStatus;
-using infiniframe::exports::RunReturnExport;
-using infiniframe::exports::RunWindowExportStatus;
-using infiniframe::exports::RunWindowReturnExport;
-using infiniframe::exports::NullToEmpty;
-using infiniframe::exports::DuplicateString;
\ No newline at end of file
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Dependencies/VENDORING.md b/src/InfiniFrame.NativeBridge/Native/src/Dependencies/VENDORING.md
index a1fddb6bb..9021e88e8 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Dependencies/VENDORING.md
+++ b/src/InfiniFrame.NativeBridge/Native/src/Dependencies/VENDORING.md
@@ -1,6 +1,6 @@
# Vendored Native Dependencies
-`src/InfiniFrame.Native/Dependencies` contains vendored native dependency artifacts used by `InfiniFrame.Native`.
+`src/InfiniFrame.NativeBridge/Native/src/Dependencies` contains vendored native dependency artifacts used by `InfiniFrame.Native`.
## Libraries
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Application/InfiniFrameApplication.cpp b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Application/InfiniFrameApplication.cpp
new file mode 100644
index 000000000..94afcb8c4
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Application/InfiniFrameApplication.cpp
@@ -0,0 +1,423 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+#include "Runtime/Internal/Application/InfiniFrameApplication.h"
+#include
+#include
+#include
+#include
+#include
+#ifdef __APPLE__
+#include
+#include
+#include "Runtime/Internal/Interop/Types/InfiniFrameWindow.h"
+#include "Runtime/Platform/Mac/Window.Cocoa.Internal.h"
+#endif
+#ifdef __linux__
+#include
+#include "Runtime/Platform/Linux/Core/UiThread.Gtk.h"
+#endif
+#ifdef _WIN32
+#include
+#include
+#include "Runtime/Platform/Windows/Window.Win32.Context.h"
+#include "Dependencies/wintoastlib/wintoastlib.h"
+#endif
+#ifdef __linux__
+#include "Runtime/Internal/Interop/Types/InfiniFrameWindow.h"
+#endif
+
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+struct InfiniFrameApplicationImpl {
+ mutable std::mutex mutex;
+ std::unordered_set windows;
+ std::unordered_set liveWindows;
+ bool registered = false;
+ bool shutdownRequested = false;
+ std::string webView2RuntimePath;
+ std::string notificationRegistrationId;
+ std::string appUserModelId;
+ std::string defaultNotificationIcon;
+ bool notificationsRegistered = false;
+#ifdef _WIN32
+ unsigned long runThreadId = 0;
+ bool running = false;
+#endif
+#ifdef __linux__
+ bool running = false;
+ std::condition_variable runCompleted;
+#endif
+#ifdef __APPLE__
+ bool running = false;
+ std::condition_variable runCompleted;
+#endif
+};
+
+namespace {
+ std::atomic applicationInstance = nullptr;
+}
+
+#ifdef _WIN32
+namespace {
+ std::wstring ToWindowsString(const char* value) {
+ if (value == nullptr || value[0] == '\0') return {};
+ const int length = MultiByteToWideChar(CP_UTF8, 0, value, -1, nullptr, 0);
+ if (length <= 1) return {};
+ std::wstring result(static_cast(length), L'\0');
+ MultiByteToWideChar(CP_UTF8, 0, value, -1, result.data(), length);
+ result.resize(static_cast(length - 1));
+ return result;
+ }
+}
+#endif
+
+InfiniFrameApplication::InfiniFrameApplication()
+ : _impl(std::make_unique()) {
+ InfiniFrameApplication* expected = nullptr;
+ if (!applicationInstance.compare_exchange_strong(expected, this, std::memory_order_acq_rel))
+ throw std::runtime_error("Only one InfiniFrameApplication may exist per process.");
+}
+
+InfiniFrameApplication::~InfiniFrameApplication() {
+ {
+ std::lock_guard lock(_impl->mutex);
+ // Native windows may outlive the logical application (notably while deferred
+ // platform teardown is completing). Do not leave them holding a raw
+ // pointer to the application being destroyed.
+ for (InfiniFrameWindow* window : _impl->windows)
+ window->DetachApplication();
+ _impl->windows.clear();
+ _impl->liveWindows.clear();
+ }
+#ifdef __APPLE__
+ // NSApplication and WKWebView are process-scoped. The logical application can
+ // be recreated, but all pooled hosts must be released before its native handle
+ // disappears so callbacks cannot outlive the application owner.
+ if ([NSThread isMainThread]) {
+ DrainPooledMacHosts();
+ } else {
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ DrainPooledMacHosts();
+ });
+ }
+#endif
+ InfiniFrameApplication* expected = this;
+ applicationInstance.compare_exchange_strong(expected, nullptr, std::memory_order_acq_rel);
+}
+
+InfiniFrameApplication* InfiniFrameApplication::GetInstance() noexcept {
+ return applicationInstance.load(std::memory_order_acquire);
+}
+
+void InfiniFrameApplication::Register() {
+ std::lock_guard lock(_impl->mutex);
+#ifdef _WIN32
+ InfiniFrameWindow::Register(GetModuleHandle(nullptr));
+ if (_impl->registered) return;
+ if (!_impl->appUserModelId.empty()) {
+ const std::wstring appUserModelId = ToWindowsString(_impl->appUserModelId.c_str());
+ const HRESULT result = SetCurrentProcessExplicitAppUserModelID(appUserModelId.c_str());
+ if (FAILED(result))
+ throw std::runtime_error("Could not set the application Windows AppUserModelID.");
+ }
+
+#endif
+#ifdef __linux__
+ // GTK/WebKit must be initialized by the application, not by the first
+ // window. This also establishes the owner thread before construction.
+ infiniframe::linux_gtk::ui_thread::EnsureInitialized();
+#endif
+#ifdef __APPLE__
+ // NSApplication is initialized exactly once for the process, while the
+ // InfiniFrame application registry remains per application instance.
+ InfiniFrameWindow::Register();
+#endif
+ _impl->registered = true;
+}
+
+void InfiniFrameApplication::Configure(const InfiniFrameApplicationInitParams& parameters) {
+ std::lock_guard lock(_impl->mutex);
+ _impl->webView2RuntimePath = parameters.WebView2RuntimePath == nullptr ? "" : parameters.WebView2RuntimePath;
+ _impl->notificationRegistrationId = parameters.NotificationRegistrationId == nullptr ? "" : parameters.NotificationRegistrationId;
+ _impl->appUserModelId = parameters.AppUserModelId == nullptr ? "" : parameters.AppUserModelId;
+ _impl->defaultNotificationIcon = parameters.DefaultNotificationIcon == nullptr ? "" : parameters.DefaultNotificationIcon;
+}
+
+#ifdef _WIN32
+void InfiniFrameApplication::EnsureNotificationsInitialized(const char* appName) {
+ std::lock_guard lock(_impl->mutex);
+ if (_impl->notificationsRegistered) return;
+ const std::string identity = !_impl->notificationRegistrationId.empty()
+ ? _impl->notificationRegistrationId
+ : !_impl->appUserModelId.empty() ? _impl->appUserModelId
+ : appName != nullptr && appName[0] != '\0' ? appName
+ : "InfiniFrame";
+
+ const char* effectiveAppName = appName != nullptr && appName[0] != '\0' ? appName : identity.c_str();
+ const std::wstring windowsAppName = ToWindowsString(effectiveAppName);
+ const std::wstring windowsIdentity = ToWindowsString(identity.c_str());
+ WinToastLib::setDebugOutputEnabled(false);
+ WinToastLib::WinToast::instance()->setAppName(windowsAppName);
+ WinToastLib::WinToast::instance()->setAppUserModelId(windowsIdentity);
+ if (!WinToastLib::WinToast::instance()->initialize())
+ throw std::runtime_error("Could not initialize application notifications.");
+ _impl->notificationsRegistered = true;
+}
+#endif
+
+void InfiniFrameApplication::Run() noexcept {
+#ifdef _WIN32
+ MSG message = {};
+ PeekMessage(&message, nullptr, WM_USER, WM_USER, PM_NOREMOVE);
+
+ {
+ std::lock_guard lock(_impl->mutex);
+ _impl->runThreadId = GetCurrentThreadId();
+ _impl->running = true;
+ // Shutdown is a drain request, not permission to abandon HWNDs. A close can
+ // be deferred while WebView2 is creating its controller, so the message pump
+ // must continue until every tracked window has reached WM_DESTROY.
+ if (_impl->windows.empty()) {
+ _impl->running = false;
+ return;
+ }
+ }
+
+ while (true) {
+ {
+ std::lock_guard lock(_impl->mutex);
+ if (_impl->shutdownRequested && _impl->windows.empty()) break;
+ }
+
+ MsgWaitForMultipleObjectsEx(0, nullptr, 50, QS_ALLINPUT, MWMO_INPUTAVAILABLE);
+ while (PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) {
+ if (message.message == WM_QUIT) {
+ std::vector windows;
+ {
+ std::lock_guard lock(_impl->mutex);
+ _impl->shutdownRequested = true;
+ windows.assign(_impl->windows.begin(), _impl->windows.end());
+ }
+ // WM_QUIT does not dispatch to HWNDs. Post WM_CLOSE so normal
+ // teardown drains every tracked window before returning.
+ for (InfiniFrameWindow* window : windows)
+ window->Close();
+ break;
+ }
+ TranslateMessage(&message);
+ DispatchMessage(&message);
+ }
+ }
+
+ std::lock_guard lock(_impl->mutex);
+ _impl->running = false;
+ _impl->runThreadId = 0;
+#else
+#ifdef __linux__
+ {
+ std::unique_lock lock(_impl->mutex);
+ _impl->running = true;
+ if (_impl->windows.empty()) {
+ _impl->running = false;
+ return;
+ }
+ lock.unlock();
+ }
+ infiniframe::linux_gtk::ui_thread::EnsureInitialized();
+ {
+ std::unique_lock lock(_impl->mutex);
+ _impl->runCompleted.wait(lock, [this] { return _impl->windows.empty(); });
+ _impl->running = false;
+ }
+#endif
+#ifdef __APPLE__
+ {
+ std::unique_lock lock(_impl->mutex);
+ _impl->running = true;
+ if (_impl->windows.empty()) {
+ _impl->running = false;
+ return;
+ }
+ }
+
+ auto stopWhenComplete = [this] {
+ std::unique_lock lock(_impl->mutex);
+ _impl->runCompleted.wait(lock, [this] { return _impl->windows.empty() || _impl->liveWindows.empty(); });
+ _impl->running = false;
+ };
+
+ void (^pumpMainRunLoop)() = ^{
+ while (true) {
+ {
+ std::lock_guard lock(_impl->mutex);
+ if (_impl->liveWindows.empty()) break;
+ }
+
+ @autoreleasepool {
+ [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode
+ beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.05]];
+ }
+ }
+ };
+
+ if ([NSThread isMainThread]) {
+ pumpMainRunLoop();
+ } else {
+ // Keep the main dispatch queue available for synchronous AppKit calls.
+ // The test host and embedding applications may already own NSApp's
+ // process loop, so pump the shared main run loop instead of entering a
+ // nested [NSApp run] invocation.
+ CFRunLoopRef mainRunLoop = CFRunLoopGetMain();
+ CFRunLoopPerformBlock(mainRunLoop, kCFRunLoopCommonModes, pumpMainRunLoop);
+ CFRunLoopWakeUp(mainRunLoop);
+ }
+ stopWhenComplete();
+#endif
+#endif
+}
+
+void InfiniFrameApplication::Shutdown() noexcept {
+ std::vector windows;
+ {
+ std::lock_guard lock(_impl->mutex);
+ _impl->shutdownRequested = true;
+ windows.assign(_impl->windows.begin(), _impl->windows.end());
+ }
+#ifdef _WIN32
+ {
+ std::lock_guard lock(_impl->mutex);
+ if (_impl->runThreadId != 0) {
+ PostThreadMessage(_impl->runThreadId, WM_QUIT, 0, 0);
+ }
+ }
+#elif defined(__linux__)
+ (void)windows;
+#elif defined(__APPLE__)
+ dispatch_async(dispatch_get_main_queue(), ^{
+ for (InfiniFrameWindow* window : windows)
+ window->Close();
+ });
+#endif
+}
+
+#ifdef __APPLE__
+void InfiniFrameApplication::WaitForWindowDestruction() noexcept {
+ auto windowsRemain = [this] {
+ std::lock_guard lock(_impl->mutex);
+ return !_impl->windows.empty();
+ };
+
+ if ([NSThread isMainThread]) {
+ while (windowsRemain()) {
+ @autoreleasepool {
+ [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode
+ beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.05]];
+ }
+ }
+ return;
+ }
+
+ std::unique_lock lock(_impl->mutex);
+ _impl->runCompleted.wait(lock, [this] { return _impl->windows.empty(); });
+}
+#endif
+
+void InfiniFrameApplication::TrackWindow(InfiniFrameWindow* window) {
+ if (window == nullptr) return;
+ std::lock_guard lock(_impl->mutex);
+ _impl->windows.insert(window);
+ _impl->liveWindows.insert(window);
+}
+
+void InfiniFrameApplication::UntrackWindow(InfiniFrameWindow* window) noexcept {
+ if (window == nullptr) return;
+ bool becameEmpty = false;
+ {
+ std::lock_guard lock(_impl->mutex);
+ _impl->windows.erase(window);
+ _impl->liveWindows.erase(window);
+ becameEmpty = _impl->windows.empty();
+ }
+#ifdef __linux__
+ // The native window destructor is the ownership boundary. Detach only
+ // after it has unregistered itself so the GTK loop cannot be joined while
+ // managed teardown still owns the native window.
+ window->DetachApplication();
+ if (becameEmpty) _impl->runCompleted.notify_all();
+#endif
+#ifdef __APPLE__
+ // Deferred destruction can outlive the logical close, but never let the
+ // window retain a pointer to an application that has unregistered it.
+ window->DetachApplication();
+ if (becameEmpty) _impl->runCompleted.notify_all();
+#endif
+#ifndef __linux__
+ (void)becameEmpty;
+#endif
+}
+
+void InfiniFrameApplication::NotifyWindowClosed(InfiniFrameWindow* window) noexcept {
+ if (window == nullptr) return;
+
+ std::lock_guard lock(_impl->mutex);
+ _impl->liveWindows.erase(window);
+#ifdef _WIN32
+ _impl->windows.erase(window);
+ window->DetachApplication();
+ if (_impl->running && _impl->liveWindows.empty()) {
+ _impl->shutdownRequested = true;
+ PostThreadMessage(_impl->runThreadId, WM_QUIT, 0, 0);
+ }
+#endif
+#ifdef __linux__
+ // Keep the registry entry until the native destructor calls
+ // UntrackWindow. This lets managed teardown complete before the GTK loop
+ // is joined, while the live-window count reflects the GTK destroy event.
+#endif
+#ifdef __APPLE__
+ // Keep the C++ object registered until deferred destruction has completed,
+ // but remove it from the live set so the final logical close ends Run().
+ if (_impl->running && _impl->liveWindows.empty()) {
+ _impl->runCompleted.notify_all();
+ }
+#endif
+}
+
+std::size_t InfiniFrameApplication::GetWindowCount() const noexcept {
+ std::lock_guard lock(_impl->mutex);
+ return _impl->liveWindows.size();
+}
+
+const char* InfiniFrameApplication::GetWebView2RuntimePath() const noexcept {
+ thread_local std::string value;
+ std::lock_guard lock(_impl->mutex);
+ value = _impl->webView2RuntimePath;
+ return value.c_str();
+}
+
+const char* InfiniFrameApplication::GetNotificationRegistrationId() const noexcept {
+ thread_local std::string value;
+ std::lock_guard lock(_impl->mutex);
+ value = _impl->notificationRegistrationId;
+ return value.c_str();
+}
+
+const char* InfiniFrameApplication::GetAppUserModelId() const noexcept {
+ thread_local std::string value;
+ std::lock_guard lock(_impl->mutex);
+ value = _impl->appUserModelId;
+ return value.c_str();
+}
+
+const char* InfiniFrameApplication::GetDefaultNotificationIcon() const noexcept {
+ thread_local std::string value;
+ std::lock_guard lock(_impl->mutex);
+ value = _impl->defaultNotificationIcon;
+ return value.c_str();
+}
+
+bool InfiniFrameApplication::HasNotificationRegistration() const noexcept {
+ return _impl->notificationsRegistered;
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Application/InfiniFrameApplication.h b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Application/InfiniFrameApplication.h
new file mode 100644
index 000000000..84003e924
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Application/InfiniFrameApplication.h
@@ -0,0 +1,50 @@
+#pragma once
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+#include
+#include
+
+#include "Runtime/Internal/Interop/Types/InfiniFrameApplicationInitParams.h"
+
+class InfiniFrameWindow;
+struct InfiniFrameApplicationImpl;
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+/**
+ * @brief Process-scoped owner and registry for native InfiniFrame windows.
+ *
+ * The registry is independent of the platform message loop so the existing
+ * window ABI can remain compatible while application-loop ownership is added.
+ */
+class InfiniFrameApplication {
+ public:
+ InfiniFrameApplication();
+ ~InfiniFrameApplication();
+
+ InfiniFrameApplication(const InfiniFrameApplication&) = delete;
+ InfiniFrameApplication& operator=(const InfiniFrameApplication&) = delete;
+
+ [[nodiscard]] static InfiniFrameApplication* GetInstance() noexcept;
+ void Register();
+ void Configure(const InfiniFrameApplicationInitParams& parameters);
+ void Run() noexcept;
+ void Shutdown() noexcept;
+#ifdef __APPLE__
+ void WaitForWindowDestruction() noexcept;
+#endif
+ void TrackWindow(InfiniFrameWindow* window);
+ void UntrackWindow(InfiniFrameWindow* window) noexcept;
+ void NotifyWindowClosed(InfiniFrameWindow* window) noexcept;
+ [[nodiscard]] std::size_t GetWindowCount() const noexcept;
+ [[nodiscard]] const char* GetWebView2RuntimePath() const noexcept;
+ [[nodiscard]] const char* GetNotificationRegistrationId() const noexcept;
+ [[nodiscard]] const char* GetAppUserModelId() const noexcept;
+ [[nodiscard]] const char* GetDefaultNotificationIcon() const noexcept;
+ [[nodiscard]] bool HasNotificationRegistration() const noexcept;
+ void EnsureNotificationsInitialized(const char* appName);
+
+ private:
+ std::unique_ptr _impl;
+};
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Utilities/ExportErrorState.cpp b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Exports/ExportErrorState.cpp
similarity index 99%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Utilities/ExportErrorState.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Exports/ExportErrorState.cpp
index 3fc39a1f6..729f2d201 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Utilities/ExportErrorState.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Exports/ExportErrorState.cpp
@@ -8,4 +8,4 @@
namespace infiniframe::exports {
thread_local std::string g_lastErrorMessage;
thread_local InteropStatus g_lastStatus = InteropStatus::Success;
-}
\ No newline at end of file
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Utilities/ExportErrorState.h b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Exports/ExportErrorState.h
similarity index 95%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Utilities/ExportErrorState.h
rename to src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Exports/ExportErrorState.h
index cf28afc77..fd1e6568e 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Utilities/ExportErrorState.h
+++ b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Exports/ExportErrorState.h
@@ -16,9 +16,8 @@
#include
#include
-#include "Runtime/Shared/Window/InfiniFrame.h"
-#include "Runtime/Shared/Utilities/InteropStatus.h"
-#include "Api/Utilities/ExportStringHelpers.h"
+#include "Runtime/Internal/Interop/Types/InteropStatus.h"
+#include "Runtime/Internal/Interop/Exports/ExportStringHelpers.h"
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Utilities/ExportExecution.h b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Exports/ExportExecution.h
similarity index 97%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Utilities/ExportExecution.h
rename to src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Exports/ExportExecution.h
index 48a985951..c3cf4a8dc 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Utilities/ExportExecution.h
+++ b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Exports/ExportExecution.h
@@ -5,8 +5,8 @@
#include
#include
-#include "Runtime/Shared/Window/InfiniFrame.h"
-#include "Runtime/Shared/Utilities/InteropStatus.h"
+#include "Runtime/Internal/Interop/Types/InfiniFrameWindow.h"
+#include "Runtime/Internal/Interop/Types/InteropStatus.h"
#include "ExportErrorState.h"
#include "ExportValidation.h"
// ---------------------------------------------------------------------------------------------------------------------
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Utilities/ExportStringHelpers.h b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Exports/ExportStringHelpers.h
similarity index 97%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Utilities/ExportStringHelpers.h
rename to src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Exports/ExportStringHelpers.h
index bb6d7fd25..8e1a5080b 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Utilities/ExportStringHelpers.h
+++ b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Exports/ExportStringHelpers.h
@@ -13,7 +13,7 @@
#include
#include
-#include "Runtime/Shared/Window/InfiniFrame.h"
+#include "Runtime/Internal/Utilities/StringCopy.h"
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Utilities/ExportValidation.h b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Exports/ExportValidation.h
similarity index 96%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Utilities/ExportValidation.h
rename to src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Exports/ExportValidation.h
index fcc949f38..29c3ccce1 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Utilities/ExportValidation.h
+++ b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Exports/ExportValidation.h
@@ -16,8 +16,7 @@
#include
#include
-#include "Runtime/Shared/Window/InfiniFrame.h"
-#include "Runtime/Shared/Utilities/InteropStatus.h"
+#include "Runtime/Internal/Interop/Types/InteropStatus.h"
#include "ExportErrorState.h"
// ---------------------------------------------------------------------------------------------------------------------
// Code
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Runtime/Shared/Types/Basic.h b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/Basic.h
similarity index 100%
rename from src/InfiniFrame.NativeBridge/Native/src/Runtime/Shared/Types/Basic.h
rename to src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/Basic.h
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Runtime/Shared/Types/Callbacks.h b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/Callbacks.h
similarity index 79%
rename from src/InfiniFrame.NativeBridge/Native/src/Runtime/Shared/Types/Callbacks.h
rename to src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/Callbacks.h
index 1fd608230..38e0edf3e 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Runtime/Shared/Types/Callbacks.h
+++ b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/Callbacks.h
@@ -4,8 +4,9 @@
// ---------------------------------------------------------------------------------------------------------------------
#include
-#include "Basic.h"
-#include "Monitor.h"
+#include "Runtime/Internal/Interop/Types/Basic.h"
+#include "Runtime/Internal/Interop/Types/Monitor.h"
+#include "Runtime/Internal/Interop/Types/CustomSchemeResponse.h"
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
@@ -63,40 +64,6 @@ using DebugEventCallback = void (*)(
const char* platformPayload
);
-/** Version 1 custom-scheme response body kinds. Kind 2 is reserved for a future pull-based stream ABI. */
-enum class CustomSchemeBodyKind : uint32_t {
- Buffered = 1,
- Stream = 2
-};
-
-using ReleaseCustomSchemeResponseCallback = void (*)(void* ownerContext);
-
-/**
- * @brief Versioned custom-scheme response descriptor shared with .NET.
- *
- * The native caller owns this descriptor. The producer owns Body, ContentTypeUtf8, and OwnerContext until native calls
- * Release(OwnerContext) exactly once. Native must not free any field directly. ReservedRead/ReservedSeek are ABI space
- * for a future streaming body kind and must be null for buffered responses.
- */
-struct CustomSchemeResponse {
- static constexpr uint32_t CurrentAbiVersion = 1;
- static constexpr uint64_t MaxBufferedBodyBytes = 256ULL * 1024ULL * 1024ULL;
-
- uint32_t StructSize;
- uint32_t AbiVersion;
- uint32_t StatusCode;
- uint32_t BodyKind;
- uint64_t ContentLength;
- const uint8_t* Body;
- const char* ContentTypeUtf8;
- void* OwnerContext;
- ReleaseCustomSchemeResponseCallback Release;
- void* ReservedRead;
- void* ReservedSeek;
-};
-
-static_assert(sizeof(uintptr_t) != 8 || sizeof(CustomSchemeResponse) == 72, "Unexpected 64-bit response ABI layout");
-
/**
* @brief Called when the WebView requests a custom-scheme resource.
* @param url Platform-native URL (UTF-8); borrowed for the duration of the call
@@ -167,4 +134,4 @@ using NavigationStartingCallback = int (*)(const char* url, int isUserInitiated,
* @param x Screen X coordinate of drop location
* @param y Screen Y coordinate of drop location
*/
-using FileDroppedCallback = void (*)(const char** paths, int count, int x, int y);
\ No newline at end of file
+using FileDroppedCallback = void (*)(const char** paths, int count, int x, int y);
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/CustomSchemeResponse.h b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/CustomSchemeResponse.h
new file mode 100644
index 000000000..5f40598cc
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/CustomSchemeResponse.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include
+
+/** Version 1 custom-scheme response body kinds. Kind 2 is reserved for streaming. */
+enum class CustomSchemeBodyKind : uint32_t {
+ Buffered = 1,
+ Stream = 2
+};
+
+using ReleaseCustomSchemeResponseCallback = void (*)(void* ownerContext);
+
+/** Versioned response descriptor shared with the managed ABI. */
+struct CustomSchemeResponse {
+ static constexpr uint32_t CurrentAbiVersion = 1;
+ static constexpr uint64_t MaxBufferedBodyBytes = 256ULL * 1024ULL * 1024ULL;
+
+ uint32_t StructSize;
+ uint32_t AbiVersion;
+ uint32_t StatusCode;
+ uint32_t BodyKind;
+ uint64_t ContentLength;
+ const uint8_t* Body;
+ const char* ContentTypeUtf8;
+ void* OwnerContext;
+ ReleaseCustomSchemeResponseCallback Release;
+ void* ReservedRead;
+ void* ReservedSeek;
+};
+
+static_assert(sizeof(uintptr_t) != 8 || sizeof(CustomSchemeResponse) == 72,
+ "Unexpected 64-bit response ABI layout");
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Runtime/Shared/Types/DialogButtons.h b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/DialogButtons.h
similarity index 100%
rename from src/InfiniFrame.NativeBridge/Native/src/Runtime/Shared/Types/DialogButtons.h
rename to src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/DialogButtons.h
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Runtime/Shared/Types/DialogIcon.h b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/DialogIcon.h
similarity index 100%
rename from src/InfiniFrame.NativeBridge/Native/src/Runtime/Shared/Types/DialogIcon.h
rename to src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/DialogIcon.h
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Runtime/Shared/Types/DialogResult.h b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/DialogResult.h
similarity index 100%
rename from src/InfiniFrame.NativeBridge/Native/src/Runtime/Shared/Types/DialogResult.h
rename to src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/DialogResult.h
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/InfiniFrameApplicationInitParams.h b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/InfiniFrameApplicationInitParams.h
new file mode 100644
index 000000000..5b4b39c36
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/InfiniFrameApplicationInitParams.h
@@ -0,0 +1,24 @@
+#pragma once
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+#include
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+/**
+ * @brief Process-wide initialization parameters for InfiniFrameApplication.
+ *
+ * Field order defines the ABI layout shared with the managed
+ * InfiniFrameNativeApplicationParameters type. New fields must be appended
+ * before StructSize and the managed/native layouts must be updated together.
+ */
+struct InfiniFrameApplicationInitParams {
+ const char* WebView2RuntimePath;
+ const char* NotificationRegistrationId;
+ const char* AppUserModelId;
+ const char* DefaultNotificationIcon;
+
+ // ABI version/size marker. This field must remain last.
+ int StructSize;
+};
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Runtime/Shared/Window/InfiniFrameWindow.h b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/InfiniFrameWindow.h
similarity index 96%
rename from src/InfiniFrame.NativeBridge/Native/src/Runtime/Shared/Window/InfiniFrameWindow.h
rename to src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/InfiniFrameWindow.h
index 4c8da0105..9238b17a2 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Runtime/Shared/Window/InfiniFrameWindow.h
+++ b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/InfiniFrameWindow.h
@@ -2,46 +2,51 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#ifdef _WIN32
-#include
-#include
-#include
-#endif
-
-#ifdef __APPLE__
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#endif
-
-#ifdef __linux__
-#include
-#include
-#endif
-
-#include