Skip to content
Shiny.NET
Shiny MAUI Shell v7 - App Links, App Shortcuts, & Navigation Interception!Shortcut me to it

Getting Started

Ship a web app — Blazor WebAssembly, React, Vue, anything that builds to static files — inside a .NET MAUI app, served from the device itself, updated from your own server, and able to call native services.

  • Served locally. A loopback Shiny.Net.HttpServer serves the app straight out of its zip. Nothing is extracted, and it works offline.
  • Updated from your server. At launch the host asks a Shiny.AppDeviceBridge.AspNetCore server whether the installed version is still acceptable. A required update downloads before the app shows. An optional one downloads in the background and applies next launch.
  • Verified. Every release is signed with ECDSA P-256 and checked against a public key compiled into the app, then checked against its SHA-256 and size before it can be served. Old releases are never reinstalled.
  • Bridged. Native services become same-origin HTTP endpoints under /_bridge, plus one Server-Sent Events stream. Each bridge is its own package and takes one extension method.
  • Every head. Android, iOS, Mac Catalyst and Windows, plus the maui-labs macOS (AppKit) and Linux (GTK4) backends.
┌─ MAUI app ───────────────────────────────────────────────────┐
│ WebAppHostView ── WebView ──▶ http://127.0.0.1:5780 │
│ │ │
│ WebAppHost ── Shiny.Net.HttpServer (loopback only) │
│ ├─ session guard launch token → HttpOnly cookie │
│ ├─ /_bridge/* device · location · BLE · push · … │
│ ├─ /_bridge/events Server-Sent Events │
│ └─ static files ◀─ ZipFileSource ◀─ baseline or download │
│ ▲ │
└──────────────────────────────────────────────┼───────────────┘
│ signed release
Shiny.AppDeviceBridge.AspNetCore server
GitHub GitHub stars for shinyorg/webapphost
Downloads NuGet downloads for Shiny.AppDeviceBridge
Frameworks
.NET MAUI
Operating Systems
Android
iOS
macOS
Windows
Linux
claude plugin marketplace add shinyorg/skills
claude plugin install shiny@shiny

One plugin installs all 37 Shiny skills. Your agent loads only the skill relevant to what you're building, so there's no cost to having them all available.

copilot plugin marketplace add https://github.com/shinyorg/skills
copilot plugin install shiny@shiny

One plugin installs all 37 Shiny skills. Your agent loads only the skill relevant to what you're building, so there's no cost to having them all available.

View shiny-appdevicebridge Skill
Package Use it in What it does
Shiny.AppDeviceBridge.Maui the app UseWebAppHost, WebAppHostView, WebAppHostPage, AllowWebPermissions, AddWebAppEndpoints, AddWebAppAuthentication, AddWebAppAuthorization
Shiny.AppDeviceBridge.Blazor the Blazor WebAssembly app AddWebAppHostClient(): the page’s transport, typed clients for the built-in bridges (IHostBridge, ISettingsBridge, IFilesBridge, ILinksBridge), WebAppEvents, WebAppNativeCalls (typed C# handlers for jobs, GPS, geofences and push), and WebAppBridge for endpoints of your own
Shiny.AppDeviceBridge.Client (dependency) the typed-client foundation: IBridgeTransport, BridgeException, the [BridgeClient] attributes and the generator that implements them, and the built-in bridges’ contracts
Shiny.AppDeviceBridge.{Bridge}.Client the web app one per bridge: its contracts and typed client — ICalendarBridge, IWifiBridge, … — registered with Add{Bridge}BridgeClient()
@shinyorg/appdevicebridge a JavaScript or TypeScript web app the same typed clients in TypeScript, generated from the same declarations (clients/typescript)
Shiny.AppDeviceBridge (dependency) host, updater, install store, session guard, bridge contracts, built-in settings and files endpoints, your own endpoints and their authentication; no MAUI dependency
Shiny.AppDeviceBridge.Core (dependency) protocol contracts, version ordering, release signatures
Shiny.AppDeviceBridge.AspNetCore your server AddWebAppReleases, MapWebAppReleases, file-system release store
Shiny.AppDeviceBridge.AppSupport the app AddAppSupportBridge() — device info, orientation, browser, maps, settings, app store, launch at login, share, haptics and vibration, connectivity, battery, screen and clipboard
Shiny.AppDeviceBridge.Locations the app AddGpsBridge(), AddGeofenceBridge(), AddLocationBridges(), AddMotionActivityBridge()
Shiny.AppDeviceBridge.BluetoothLE the app AddBluetoothLEBridge()
Shiny.AppDeviceBridge.Obd the app AddObdBridge(): OBD-II over Bluetooth LE or Wi-Fi adapters — decoded PIDs, VIN, trouble codes, live readings
Shiny.AppDeviceBridge.Wifi the app AddWifiBridge(hotspot: false): current network and changes, scan, connect, known networks, radio, hotspot
Shiny.AppDeviceBridge.Discovery the app AddDiscoveryBridge(DiscoveryProtocols.All): mDNS/Bonjour, SSDP/UPnP, WS-Discovery search, browse, resolve and publish
Shiny.AppDeviceBridge.Jobs the app AddWebAppJob(name, configure): background jobs handled by the page or background.js
Shiny.AppDeviceBridge.Push the app AddPushBridge(): register, unregister, token, tags, and optionally push payloads for the web app
Shiny.AppDeviceBridge.Notifications the app AddNotificationsBridge(): local notifications now, scheduled, repeating or at a geofence; pending, cancel, badge, channels; taps handed to the web app
Shiny.AppDeviceBridge.HttpTransfers the app AddHttpTransfersBridge(): background uploads and downloads to and from file roots, with progress events and completion handlers
Shiny.AppDeviceBridge.AppLinks the app AddAppLinksBridge(o => o.Schemes.Add("myapp")): deep links and universal/app links routed to the page
Shiny.AppDeviceBridge.Health the app AddHealthBridge(): HealthKit and Health Connect permissions, bucketed reads, writes and live readings
Shiny.AppDeviceBridge.Speech the app AddSpeechBridge(): on-device speech recognition, dictation as events, text-to-speech, voices
Shiny.AppDeviceBridge.Contacts the app AddContactsBridge(): access, paged search, read, photos, create, update and delete (Android, iOS)
Shiny.AppDeviceBridge.Calendar the app AddCalendarBridge(): access, calendars, events in a date range, create, update and delete
Shiny.AppDeviceBridge.Photos the app AddPhotosBridge(): the system photo picker, and the photo library — pages, thumbnails and full-size exports — as files in a file root
Shiny.AppDeviceBridge.Folders the app AddFoldersBridge(): the platform’s folder picker, with each picked folder remembered as a file root across launches
Shiny.AppDeviceBridge.TrayIcon the app AddTrayIconBridge(): system tray / menu bar icons, menus, badges, notifications and animation, with clicks handed back to the web app
builder
.UseMauiApp<App>()
.UseWebAppHost(o =>
{
o.AppId = "field-app";
o.UseBaseline(typeof(App).Assembly, "webapp.zip", "1.0.0"); // runs offline on first launch
o.UpdateServer = new Uri("https://api.example.com/webapps");
o.PublicKey = """
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----
""";
})
.AddAppSupportBridge()
.AddLocationBridges()
.AddBluetoothLEBridge();
public class App : Application
{
protected override Window CreateWindow(IActivationState? state) => new(new WebAppHostPage());
}

Updates are optional. Without an UpdateServer nothing is checked, downloaded or signed, and the app simply serves the zip compiled into it — which is a complete setup on its own:

o.AppId = "field-app";
o.UseBaseline(typeof(App).Assembly, "webapp.zip"); // version defaults to 1.0.0

There is no manifest, no signing key and no network at any point; the install directory is never even created. Add UpdateServer and PublicKey later and the embedded build becomes the floor that downloads are compared against, which is when the version argument starts to matter.

Each bridge extension also registers the Shiny service behind it, and calls UseShiny() if nothing has yet. Don’t add AddGps(), AddGeofencing() or AddBluetoothLE() yourself. Where a platform has no implementation, that bridge’s endpoints return 501 and GET /_bridge/host reports it as unsupported.

Embed the baseline zip with a LogicalName:

<EmbeddedResource Include="webapp.zip" LogicalName="webapp.zip" />

The zip can hold the files at its root or under wwwroot/. A zipped Blazor publish works either way, and its precompressed .br/.gz files are served as they are.

Platform Required
Android Cleartext to 127.0.0.1: a network security config (see the sample) or usesCleartextTraffic
iOS / Mac Catalyst NSAppTransportSecurityNSAllowsLocalNetworking
Mac Catalyst, sandboxed macOS com.apple.security.network.server entitlement

Plus the usage descriptions and permissions for whichever bridges you add.

Every bridge has a typed client — see Typed clients. A Blazor WebAssembly app registers the ones it uses and injects them:

builder.Services
.AddWebAppHostClient()
.AddAppBridgeClient()
.AddGpsBridgeClient();
@inject IAppBridge App
var info = await App.GetInfoAsync();

Any other web app uses the same clients from @shinyorg/appdevicebridge:

import { AppBridge } from "@shinyorg/appdevicebridge";
const info = await new AppBridge().getInfo();