PeakSWC.RemoteBlazorWebView.RemoteComponents
10.0.15.1
See the version list below for details.
dotnet add package PeakSWC.RemoteBlazorWebView.RemoteComponents --version 10.0.15.1
NuGet\Install-Package PeakSWC.RemoteBlazorWebView.RemoteComponents -Version 10.0.15.1
<PackageReference Include="PeakSWC.RemoteBlazorWebView.RemoteComponents" Version="10.0.15.1" />
<PackageVersion Include="PeakSWC.RemoteBlazorWebView.RemoteComponents" Version="10.0.15.1" />
<PackageReference Include="PeakSWC.RemoteBlazorWebView.RemoteComponents" />
paket add PeakSWC.RemoteBlazorWebView.RemoteComponents --version 10.0.15.1
#r "nuget: PeakSWC.RemoteBlazorWebView.RemoteComponents, 10.0.15.1"
#:package PeakSWC.RemoteBlazorWebView.RemoteComponents@10.0.15.1
#addin nuget:?package=PeakSWC.RemoteBlazorWebView.RemoteComponents&version=10.0.15.1
#tool nuget:?package=PeakSWC.RemoteBlazorWebView.RemoteComponents&version=10.0.15.1
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.15)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.15)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on PeakSWC.RemoteBlazorWebView.RemoteComponents:
| Package | Downloads |
|---|---|
|
PeakSWC.RemoteBlazorWebView.Wpf
This package will transport the user interface from an app built using BlazorWebView to a server running in the cloud. This allows an app running behind a firewall to be accessed through a browser by accessing the cloud server. |
GitHub repositories
This package is not used by any popular GitHub repositories.
Based on maui 10.0.15.1
Changes since 10.0.14
Merge branch 'claude/session-bar-opt-in': session bar is opt-in, templates, restart keeps the owner role (10.0.15.1)
A page comes back from a restart with the query it was opened with
Session bar is opt-in: no placeholder, no bar; a template makes it the app's own
Merge branch 'claude/session-bar-slot-declared': session bar waits for a declared placeholder (10.0.15.1)
Generator README and ShareControl doc comment mention the host-page tag for the session bar
Version 10.0.15.1 (was 10.0.16): the first three parts follow the .NET release
Test: visibility waits take their own options type
Session bar waits for a declared placeholder instead of floating at startup (10.0.16)
Release workflow publishes to NuGet when dry_run is off (trusted publishing, nuget-org approval)
10-client test waits 2 minutes before attaching browsers again (idle sessions must survive)
Server stats tests: the app's own blazor.modules.json is served now (2 bytes fewer per client)
ParticipantsChanged needs relay 10.0.15
Merge PR #85 (module, JS, relay, lifecycle, generator and system test fixes)
README and release notes: sign-in providers, owner key, ParticipantsChanged
Authorization is injected, not compiled in: None, Fake (tests) and AzureB2C providers
Tests: owner URL carries the key; spoofed-owner test waits for the roster
WinForms: OwnerKey is not a designer property
WIP: owner key, names in the roster, ParticipantsChanged, session group check
Remove UTF-8 BOM from embedded blazor.modules.json and clear CS8602 warnings
Use bundled protoc 3.x binary in scripts/protoc.js on Linux CI
Fix bugs across module, JS, relay, lifecycle, generator, and system tests
Browser swarm keeps 300 separate clients, spread over 10 Chromium processes
Fix RunTests.ps1 packaging/tool issues, concurrent browser tests, and MSB3277 warnings
RunTests: -NoBuild runs the tests on what the last run built
Lost-connection test cuts the viewer's connections itself
A lost connection is shown and a /mirror page comes back; unattended hand-over retried; frames follow the latest control change
Web.JS test file as EditWebView writes it
EditWebView keeps this repository's own changes across a run
Upstream source from MAUI 10.0.130 and aspnetcore 10.0.15 (EditWebView run)
Document EditWebView in the release process.
EditWebView: ask for tag dates only on the wanted major version, and print progress
EditWebView prints what a run changed; Web.JS/src and test no longer git-ignored
update packages
EditWebView: fall back to the GitHub CLI sign-in when GITHUB_PAT is not set
Microsoft 10.0 libraries to 10.0.15, SDK 10.0.404; EditWebView re-applies the Web.JS patches
Control test: a browser that has not been told yet is 'not agreed', not an error
Restart test: wait for the restart to start before waiting for the page to return
RunTests: -Filter for a partial browser run; restart test keeps the bar in view
Agent: hand the requester the session's join link
Generator: voice, run-time choices, app-supplied startup values, and a restart that survives a stuck window
Library: a restart request still restarts an app whose UI thread is stuck
Session bar: opt-in Restart button
Shared control: one change of controller at a time
Voice: a mirror page gets the remote end whenever nobody holds it
Relay: every write to the app's stream goes through one guarded method
Shared control: a message from before a hand-off can no longer replace a real one
RunTests: quote the -p: switch so PowerShell passes it whole
RunTests: run every test project, quick suites first
BugBountyTests: the mirror-scroll test ignores the control notices browsers are now sent
ControlTransferTests: the fake browser stream accepts the relay's token overload
Tests for sharing control: relay policy, render acks, URL loading, and end-to-end browser tests
Full list: https://github.com/budcribar/RemoteBlazorWebView/compare/10.0.14...227d88ffc73906e1f5d6999da10e6962b1fe51cb