PeakSWC.RemoteBlazorWebView.RemoteComponents
10.0.14
dotnet add package PeakSWC.RemoteBlazorWebView.RemoteComponents --version 10.0.14
NuGet\Install-Package PeakSWC.RemoteBlazorWebView.RemoteComponents -Version 10.0.14
<PackageReference Include="PeakSWC.RemoteBlazorWebView.RemoteComponents" Version="10.0.14" />
<PackageVersion Include="PeakSWC.RemoteBlazorWebView.RemoteComponents" Version="10.0.14" />
<PackageReference Include="PeakSWC.RemoteBlazorWebView.RemoteComponents" />
paket add PeakSWC.RemoteBlazorWebView.RemoteComponents --version 10.0.14
#r "nuget: PeakSWC.RemoteBlazorWebView.RemoteComponents, 10.0.14"
#:package PeakSWC.RemoteBlazorWebView.RemoteComponents@10.0.14
#addin nuget:?package=PeakSWC.RemoteBlazorWebView.RemoteComponents&version=10.0.14
#tool nuget:?package=PeakSWC.RemoteBlazorWebView.RemoteComponents&version=10.0.14
Remote components
Run a Blazor component in its own process and show it inside your app, in a sandboxed iframe. Use it when a piece of UI should be isolated from the app: it can run with fewer rights than the app, crash without taking the app down, and can't touch the app's page.
app process component process (RemoteComponentHost)
┌──────────────────────────────┐ stdin: "set" ┌──────────────────────────────┐
│ <MyFrame Open=... Value=...> │ ────────────────▶ │ MyReceiver (contract values) │
│ iframe sandbox, opaque │ ◀──────────────── │ root component (your UI) │
│ origin, height only │ stdout: "call" │ RemoteBlazorWebView client │
└──────────────┬───────────────┘ └──────────────┬───────────────┘
└──── iframe src = <relay>/<id> ◀── relay ◀────────┘
- Data goes over the process's stdin/stdout, newline-delimited JSON (
RemoteComponentMessage): the app sendssetwith the contract values that changed, the component sendscallfor a callback. Nothing crosses through the browser, so when the app is viewed remotely, the remote browser can't forge either side. - The iframe is sandboxed without
allow-same-origin(RemoteComponentFrame.DefaultSandbox), so it has an opaque origin. The onlypostMessagethe app's page accepts from it is its height (rbwv-remote-height), matched by the sending window.
Mirrors
When the app's page is mirrored (a read-only /mirror/<id> view), the mirror renders the same page, frame included. If the mirror
loaded the component's page as well, the relay would take that as a reload and the component's process would restart. So the frame
is rendered without src: data-rbwv-src holds the component's page, and data-rbwv-mirror-src the relay's read-only view of it
(RemoteComponentSession.MirrorSource, by default <relay>/mirror/<id>). The app's page loads the first (remotecomponents.js); a
mirror page loads the second (the RemoteWebView script), or nothing when the session has no mirror source. The component's process
allows mirrors by default (RemoteComponentHostOptions.EnableMirrors). Mirrors can't click or type in it, like the rest of the mirror.
A contract, and what the generator writes from it
Declare what crosses between the two processes as an interface, in a library both sides reference:
[RemoteComponentContract]
public interface IClock
{
string TimeZone { get; } // a value: app -> component
Alarm? NextAlarm { get; } // any JSON-serializable type
Task Snooze(); // a callback: component -> app
Task Picked(DateTimeOffset when); // at most one argument
}
The source generator in this package writes:
ClockFramefor the app, a component with[Parameter] TimeZone,NextAlarm,EventCallback SnoozeandEventCallback<DateTimeOffset> Picked:<ClockFrame Open="ct => launcher.StartAsync(ct)" TimeZone="@zone" NextAlarm="@alarm" Snooze="OnSnooze" Title="Clock" />ClockReceiverfor the component's process: implementsIClockwith the values the app sent (Changedfires when they change); callingSnooze()sends the callback. Register it in the host and injectIClock(orClockReceiver) in the UI:return RemoteComponentHost.Run(new RemoteComponentHostOptions { ServerUri = relay, GrpcBaseUri = grpc, Id = id, ExitWhenInputCloses = true, RootComponent = typeof(ClockRoot), ConfigureServices = (services, connection) => services.AddRemoteComponentReceiver<ClockReceiver>(connection), });
Values and arguments must be JSON-serializable (RemoteComponentJson.Options: web defaults, enums as strings). Values are sent
when they change (compared as JSON), all of them the first time. Rules the generator enforces: RBWRC001 for a member that isn't a
get-only property or a Task method with at most one argument, RBWRC002 for a generic or nested interface, RBWRC003 for a member name
already defined on RemoteComponentFrameBase or RemoteComponentReceiverBase. Names can be set with
[RemoteComponentContract(FrameName = "...", ReceiverName = "...")].
Razor compiles before source generators run, so a .razor file can't use a frame generated in its own project: keep the contract
in a separate library.
The pieces
| Side | Type | Package |
|---|---|---|
| App | RemoteComponentFrameBase: the iframe, sends values, raises callbacks. Open starts the process and returns a RemoteComponentSession (the frame's address and the RemoteComponentConnection to the process) |
this one |
| Component process | RemoteComponentHost.Run(options): the never-shown WPF window with a RemoteBlazorWebView client of the relay; exits when the frame closes or reloads, or the app exits |
PeakSWC.RemoteBlazorWebView.Wpf |
| Component process | RemoteComponentReceiverBase: holds the values the app sent, raises Changed, sends callbacks; services.AddRemoteComponentReceiver<T>(connection) |
this one |
How the component's process notices that the app has gone: with ExitWhenInputCloses = true it exits when its stdin ends, which
Windows does as soon as the app exits, crashes or is killed (the app must keep its end of the pipe open while the component is shown,
as RemoteComponentSession does). Without a pipe on stdin, ParentProcessId polls the process list every 2 seconds instead.
How the app starts the process (token, folder, relay) is up to it: Open is a function you pass to the frame. The relay is usually
an embedded one (PeakSWC.RemoteWebViewService.Lib) with FrameAncestors set to the app's origin and AllowOpaqueOrigin = true.
The component's host page
Include the height script (RemoteComponentFrame.HeightScript) so the app can size the frame:
<script>
(function () {
var last = 0;
function post() {
var h = Math.ceil(document.documentElement.scrollHeight);
if (h !== last) { last = h; window.parent.postMessage({ type: 'rbwv-remote-height', height: h }, '*'); }
}
if (window.ResizeObserver) new ResizeObserver(post).observe(document.documentElement);
window.addEventListener('load', post);
})();
</script>
<script src="_framework/blazor.webview.js"></script>
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.AspNetCore.Components.Web (>= 10.0.14)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.14)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 10.0.14 | 50 | 11/8/2026 |
Based on maui 10.0.14
Changes since 10.0.13
Merge pull request #79 from budcribar/claude/escape-release-notes
Escape commas and semicolons in the release notes passed to dotnet pack
Merge pull request #78 from budcribar/claude/fix-webview-version-test
Expect WebView 10.0.14 in the module serving version test
Merge pull request #76 from budcribar/fix/nuget-release-readiness
Merge pull request #75 from budcribar/claude/remote-component-exit-on-eof
Prepare NuGet release packaging, CI test coverage, and remote component contract hardening
RemoteComponentHost: ExitWhenInputCloses, exit when stdin ends instead of polling
Merge pull request #73 from budcribar/claude/agent-registry-experimental
Merge pull request #74 from budcribar/claude/mirror-safe-remote-frames
Keep remote component frames from restarting when the app is mirrored
Mark agent registry (AgentHub/AgentAdmin) experimental (RWV001)
Merge pull request #72 from budcribar/claude/fix-authorization-build
Bump NuGet packages to latest stable (staying on .NET 10)
Restore Authorization package references in RemoteWebViewService.Lib
Merge pull request #70 from budcribar/claude/remote-components-generator
Merge remote-tracking branch 'origin/net10' into claude/remote-components-generator
Merge pull request #69 from budcribar/claude/remote-components
Merge remote-tracking branch 'origin/net10' into claude/remote-components
Merge pull request #68 from budcribar/claude/remote-blazor-generator
Merge pull request #71 from budcribar/claude/generic-wording
Describe agents and the embedded relay without naming one app
Remote components README: use a generic contract example
Merge branch 'claude/remote-components' into claude/remote-components-generator
Remote components: describe the use generically
Add the [RemoteComponentContract] source generator
Add remote components: runtime and the WPF RemoteComponentHost
Add ReverseMirror to [RemoteBlazor]
Add the [RemoteBlazor] source generator
Port the net9 test-runner and window-sizing fixes.
Install nvm-windows when RunTests.ps1 cannot find nvm.
Throw the frame-ancestor error after the scan.
Name the first invalid frame ancestor after valid ones.
Merge pull request #67 from budcribar/net10-bugbounty-fixes
Port Bug02, Bug03 (dispose-on-throw), Bug06, Bug15, Bug44, and Bug60 fixes and regression tests
Set the net10 packages to 10.0.14 and the current Microsoft 10.0 libraries.
Fix Sonar reliability and security findings that are safe to backport.
Merge pull request #64 from budcribar/claude/net10-opaque-origin-frames
Let pages in opaque-origin sandboxed iframes connect
Merge pull request #62 from budcribar/claude/net10-embedded-server
Merge net10 (scroll sync) into claude/net10-embedded-server
Synchronize primary browser scrolling to read-only mirrors
EmbeddedRelayWpf: embed wwwroot so a single-file publish works
EmbeddedRelayWpf: target Windows 10 SDK and show crashes
Copy RemoteWebViewService.Lib into the Docker build and server CI paths
Split RemoteWebViewService into an embeddable library for net10
Gate AgentAdmin starts with AGENT_ADMIN_KEY and guard in-flight starts
Add the agent registry (AgentHub / AgentAdmin) to RemoteWebViewService
Add in-memory event log provider for GetLoggedEvents on Linux
Add Linux container mode with Kestrel TLS/HTTP2 for Railway TCP proxy.
Fail the performance check when the baseline has no samples.
Add the net10 intermittent CI failure log.
Add the package release workflow for the net10 line.
Read Chrome output on a dedicated thread before waiting for DevTools.
Fail the module-serving step when either test suite fails.
Port the Chrome module initializer tests onto net10.
Measure TestConnectedEvent from the Connected signal, not the pause.
Cancel the WhenAny delays in the lifecycle tests.
Assert that ping completed after shutdown.
Use TheoryData for the file-read failure cases.
Full list: https://github.com/budcribar/RemoteBlazorWebView/compare/10.0.13...66928b247e28dcae5b286883f208c8ae202ed973