Skip to main content

shiny-appdevicebridge

Generate code using Shiny.AppDeviceBridge, device bridges on your app's own Shiny.Net.HttpServer that also host a web app (Blazor WebAssembly, React, Vue, any static build) inside a .NET MAUI app on Android, iOS, Mac Catalyst, Windows and the maui-labs macOS and Linux heads — served from a loopback HTTP server, updated over the air from a signed release server, GitHub releases or a provider of your own, and given device access through bridges with typed C# and TypeScript clients

Quellinformationen

Repository
shinyorg/appdevicebridge
Letzte Quellaktivität
14. Oktober 2026 um 20:38
Erkannte Sprache von SKILL.md
Englisch
Sterne
0
Forks
0

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
shiny-appdevicebridge
description
Generate code using Shiny.AppDeviceBridge, device bridges on your app's own Shiny.Net.HttpServer that also host a web app (Blazor WebAssembly, React, Vue, any static build) inside a .NET MAUI app on Android, iOS, Mac Catalyst, Windows and the maui-labs macOS and Linux heads — served from a loopback HTTP server, updated over the air from a signed release server, GitHub releases or a provider of your own, and given device access through bridges with typed C# and TypeScript clients
auto_invoke
true
triggers
["Shiny.AppDeviceBridge","AppDeviceBridge","UseAppDeviceBridge","AddAppDeviceBridge","AppDeviceBridgeBuilder","MauiAppDeviceBridgeBuilder","AddBridge","AppDeviceBridgeOptions","AppDeviceBridgeServer","AppDeviceBridgePolicies","AuthorizeBridges","AllowAnyCallerInDebug","BridgeCallers","IAppDeviceBridgeServerExtension","IWebAppMainThread","IWebAppBackgroundInvoker","Shiny.AppDeviceBridge.WebView","WebAppHost","WebAppHostView","WebAppHostPage","WebAppHostOptions","UseBaseline","ServeWebAppRemotely","ServeWebAppLocally","OnPrepareResponse","ContentTypeOverrides","SelectVariant","client variants","mobile and desktop client","AddWebAppReleases","MapWebAppReleases","IWebAppBridge","[Truncated]"]
# Shiny.AppDeviceBridge You are an expert in Shiny.AppDeviceBridge. Use this skill when the user hosts a web app inside a .NET MAUI app, calls device features from that web app, updates it over the air, or writes a bridge of their own. **Documentation:** https://shinylib.net/appdevicebridge ## The shape of it - `Shiny.AppDeviceBridge` is the **bridge server**, on the app's own Shiny.Net.HttpServer: `http.AddAppDeviceBridge(bridge => …)` on the `ShinyHttpServerBuilder` from `services.AddShinyHttpServer(http => …)`. The server's address, port, TLS, limits, authentication and the app's own endpoints are configured on that same builder; `AppDeviceBridgeOptions` holds only the bridges' concerns (app id, mount points, allowed hosts, the bridge policy), set with `bridge.Configure(o => …)`. `UseAppDeviceBridge` in MAUI calls `AddShinyHttpServer` for you. - **Bridges register on the bridge builder, never on `MauiAppBuilder` or `ShinyHttpServerBuilder`.** `AppDeviceBridgeBuilder` has `Http`, `Services`, `Options`, `Configure(Action<AppDeviceBridgeOptions>)` and `AddBridge<TBridge>()`; creating one registers the bridge server with the built-in host, settings, files and invoke bridges. In MAUI, both `UseAppDeviceBridge` overloads — `(bridge => …, webApp => …)` with a web app, `(bridge => …)` without — hand out a `MauiAppDeviceBridgeBuilder` (adds `Maui`, the `MauiAppBuilder`) and do the host work once: `UseShiny()` on every head (guarded, so an app that already calls it is fine), Shiny.Core's `IBattery` and `IConnectivity`, and the app's dispatcher registered as `IWebAppMainThread`. **Never generate `UseShiny()` for the bridges.** Calling `UseAppDeviceBridge` again adds to the same server. - **Two kinds of bridge package.** - **No MAUI** — BluetoothLE, Beacons, Obd, Printers, Printing, Discovery, Wifi, HttpTransfers, Database, Jobs (plain `net10.0`), Gps (GPS/motion), Geofencing, RegionGeofencing, Notifications, Push, Wearables, LiveActivities, InAppPurchases, GameCenter, CallDirectory, Speech, ScreenRecorder, Calendar, Contacts, Health, RpiCamera, Tunnel. They reference only `Shiny.AppDeviceBridge`; their extensions are generic (`TBuilder AddGpsBridge<TBuilder>(this TBuilder bridge) where TBuilder : AppDeviceBridgeBuilder`) and return the builder they were given, so they chain on either builder and run headless. - **MAUI** — AppLinks, Camera, Photos, Folders, Desktop (tray, quick entry), and the device and sensors bridges inside `Shiny.AppDeviceBridge.Maui` itself. They reference `Shiny.AppDeviceBridge.Maui`, extend `MauiAppDeviceBridgeBuilder`, and do their own MAUI registration through `bridge.Maui` (camera control, tray icon, controls, Essentials, lifecycle events). The app calls none of that. - `AllowWebPermissions` and `UseTrafficMonitor` stay on `MauiAppBuilder`; `AddTrafficRecorder` stays on `ShinyHttpServerBuilder`. - For the page's own position, prefer `navigator.geolocation` with `AllowWebPermissions(WebAppWebPermissions.Geolocation)` over the GPS bridge. It works on every head except Linux, including macOS (AppKit), where the host answers it from CoreLocation. Declare `NSLocationWhenInUseUsageDescription` on Apple platforms. - Every bridge route requires `AppDeviceBridgePolicies.Bridges`, which the bridges enforce themselves. - **There is no private server.** Never generate `o.Server`, `o.ConfigureServer`, `o.AddAuthentication` or `o.AddAuthorization` on `AppDeviceBridgeOptions` — they do not exist. Use the builder. - `Shiny.AppDeviceBridge.WebView` adds the **web app host** — the `UseAppDeviceBridge(bridge => …, webApp => …)` overload (bridge delegate first, both required; `http.AddAppDeviceBridge(bridge => …, webApp => …)` without MAUI). One delegate registers the bridges alone; a second configures the web app. It serves the web app straight from a zip (the baseline or a signed download), shown in `WebAppHostView` / `WebAppHostPage`. The WebView trades a one-time launch token for an HttpOnly cookie, which the host adds to the bridge policy. Only the entry document gets `Cache-Control: no-cache`; the host sets no cache header on any other file. `WebAppHostOptions.OnPrepareResponse` runs after that for every file (an app's own cache policy for LAN/tunnel callers), and `ContentTypeOverrides` maps extensions (with the dot) to content types. - Who gets the pages: the WebView (launch session) always; other machines and tunnels only with `ServeWebAppRemotely = true`; a browser on the same device only with `ServeWebAppLocally = true`. Neither option opens the bridges — that is `AuthorizeBridges`. Never generate a custom middleware to let a local browser in. - **Two clients (phone and desktop) at the same URLs**: `webApp.Variants("mobile", "desktop")` plus `webApp.SelectVariant = ctx => …` (cookie, `Sec-CH-UA-Mobile`, `User-Agent`), with one zip holding `mobile/` and `desktop/`. Never generate a second mount point, a redirect to `/desktop`, or two web app hosts for this. The first variant is the default; a null/unknown/throwing selector falls back to it. - The server works without the WebView: bridges only, for callers the policy admits. - **Moving the server at runtime** (a LAN switch, a port setting) is `await http.StopAsync(); http.Options.Address = …; http.Options.Port = …; await http.StartAsync();` on the app's `HttpServer`. `Origin`, the WebView session and an open tunnel follow on their own; never re-register the bridges or rebuild the host to change the port. - **Bridges** are HTTP endpoints under `/_bridge/{name}` (the prefix is configurable) plus one Server-Sent Events stream. One package per bridge, one extension method each on the bridge builder. Host, settings, files and invoke are built in. `AddAppBridge()` (`app/…`, over Shiny.Core) ships with `Shiny.AppDeviceBridge`; `AddDeviceBridge()` (`device/…`, over MAUI Essentials) and `AddSensorsBridge()` (`/_bridge/sensors`) ship with `Shiny.AppDeviceBridge.Maui`. - **The event stream carries only named topics.** `GET /_bridge/events?topics=a,b` opens it; the first event, `bridge.stream`, carries the id for `PUT /_bridge/events/{id}` `{ "topics": [...] }`. The host runs each topic's native source only while a stream names it, and unhooks it when the page drops it, disconnects, or the source throws (`bridge.error { event, message }`). The typed clients manage topics — never hand-write `new EventSource(...)` in a page that has them. - **Subscribe before starting a session.** BLE scans and characteristic notifications, Health listeners, the OBD monitor, Discovery browses and dictation answer `409 not_listening` without a listener for their result event, and stop by themselves once it has none; each sensor stops once its event has none. `await` the subscription (it resolves once the host is delivering), then start. - **Every bridge has a typed client.** Never generate `fetch("/_bridge/…")` or JSON-object bodies in page code; use the bridge's client — C# for Blazor, TypeScript for everything else. ## The app (MAUI) ```csharp builder .UseMauiApp<App>() .UseAppDeviceBridge( bridge => bridge .Configure(o => o.AppId = "field-app") // AppId, BasePath, AllowedHosts, AuthorizeBridges .AddAppBridge() .AddDeviceBridge() .AddGpsBridge() .AddGeofenceBridge() .AddCalendarBridge() .AddPhotosBridge() .AddFoldersBridge(), webApp => { webApp.UseBaseline(typeof(App).Assembly, "webapp.zip"); // offline, no update provider needed // webApp.UpdateProvider = new ReleaseServerUpdateProvider(new Uri("https://api.example.com/webapps"), "-----BEGIN PUBLIC KEY-----…"); // webApp.UpdateProvider = new GitHubReleasesUpdateProvider("https://github.com/acme/field-app"); } ); // No web app, bridges only: the other overload, with just the bridge delegate. // builder.UseAppDeviceBridge(bridge => bridge.Configure(o => o.AppId = "kiosk").AddGpsBridge()); // A head adding its own bridges (desktop only, say) adds to the same server: // builder.UseAppDeviceBridge(bridge => bridge.AddTrayIconBridge()); // The server itself — address, auth, the app's own endpoints — on its builder, in any order: builder.Services.AddShinyHttpServer(http => { http.Options.Address = IPAddress.Any; http.AddAuthentication().AddApiKey(k => k.AddKey(key, "kiosk")); http.Configure(server => { server.UseAuthentication(); server.UseAuthorization(); server.MapGet("/api/orders", ctx => …).RequireAuthorization(); }); }, autoStart: false); // MAUI runs no hosted services; UseAppDeviceBridge starts the server public class App : Application { protected override Window CreateWindow(IActivationState? state) => new(new WebAppHostPage()); } ``` Updates come from `webApp.UpdateProvider` (an `IUpdateProvider`): `ReleaseServerUpdateProvider(uri, publicKey)` for the signed ASP.NET Core release server (`Channel` for prereleases), `GitHubReleasesUpdateProvider("https://github.com/owner/repo")` for GitHub releases (`AssetName`, `IncludePrereleases`, `TagPrefix`, `Token`, `RequiredWhen`), or a class of the app's own: `GetUpdateInfoAsync(Version host, WebAppVersion? app, ct)` returns an `UpdateInfo` (derive it to carry a download URL; null = nothing newer) and `DownloadAsync(update, ct)` returns the zip stream. The host enforces newer-only, the size/`Sha256` when given, and the archive opening; an exception is offline, `InvalidDataException` rejects. Never generate `webApp.UpdateServer`, `PublicKey`, `Channel` or `HttpMessageHandlerFactory` — those options don't exist. - `UseAppDeviceBridge` listens on loopback port 5780 unless the app sets a port of its own. - Without MAUI: `services.AddShinyHttpServer(http => http.AddAppDeviceBridge(bridge => bridge.Configure(o => …).AddRpiCameraBridge().AddTunnel().AddBridge<ClipboardBridge>()))`. A non-MAUI web host is the same overload on the server's builder: `http.AddAppDeviceBridge(bridge => …, webApp => …)`. - Bridge extensions register the Shiny service behind them. Do **not** also call `AddGps()`, `AddBluetoothLE()`, `AddBeaconRanging()` and so on. - A platform without an implementation answers `501`; `IHostBridge.GetInfoAsync()` lists every bridge with `IsSupported`. - Platform setup is the underlying library's: usage descriptions, manifest permissions, entitlements. Loopback needs cleartext to `127.0.0.1` on Android and `NSAllowsLocalNetworking` on Apple platforms. ## Bridge packages | Package | Registration (on the bridge builder) | Client package / interface | | --- | --- | --- | | built in | (always) | `Shiny.AppDeviceBridge.Client`: `IHostBridge`, `ISettingsBridge`, `IFilesBridge`, `ILinksBridge` | | `Shiny.AppDeviceBridge` (the server) | `AddAppBridge(store => store.AppleAppId = …, startup => startup.Arguments.Add(…))` | `Shiny.AppDeviceBridge.Client`: `IAppBridge` (`AppJsonContext`) — info, orientation, screen (`app.screen`), settings, store (Shiny.AppStore's `IAppStore`; 501 unless `store` is passed; `OpenReviewPageAsync`), launch at login (Shiny.Core's `IAppStartupInstaller`; 409 `startup_failed`), battery (`app.battery` carries `BatteryInfo` with energy saver) and connectivity, from Shiny.Core's `IPlatform`, `IScreenInfo`, `IBattery` and `IConnectivity`. No MAUI; a host without MAUI calls `services.AddBattery()` / `AddConnectivity()` itself | | `Shiny.AppDeviceBridge.Maui` | `AddDeviceBridge()`, `AddSensorsBridge()` | `Shiny.AppDeviceBridge.Client`: `IDeviceBridge` (`DeviceJsonContext`) — browser (`http`/`https`/`mailto`/`tel` only), map, share (text or file roots, ≤ 20), haptics, vibrate (≤ 5 s), keep-awake (`PUT` 501 where it does not hold), clipboard (≤ 1 MB), over MAUI Essentials, each 501 alone where missing; `ISensorsBridge` — start a sensor with a speed and `MinIntervalMs`, readings only as events (`OnCompassAsync`, …), each stopped once nothing listens to its event | | `.Gps` | `AddGpsBridge()`, `AddMotionActivityBridge()`, `AddGeocodingBridge()` | `IGpsBridge`, `IMotionBridge`, `IGeocodingBridge` (`GpsJsonContext`): `ReverseGeocodeAsync(latitude, longitude)` returns `Placemark`s from the platform geocoder (MapKit on iOS/Mac Catalyst, Android's Geocoder) or OpenStreetMap's Nominatim everywhere else, so it works on every platform; `services.AddGeocoding(o => o.BaseUri = …)` before the bridge points Nominatim at your own server. It needs network but no location permission, and fails with 503 `geocoder_unavailable` when offline. Address → position is the Maps package's `IDirectionsBridge.GeocodeAsync`, not this | | `.Geofencing` | `AddGeofenceBridge()` | `IGeofencesBridge` (`GeofencingJsonContext`) | | `.RegionGeofencing` | `AddRegionGeofenceBridge(cfg => cfg.AddGeocoderRegionSet(name, GeocoderRegionLevel.X) / .AddDocumentRegionSet<T>(name, idSelector, nameSelector, withinMeters:, filter:) / .AddNorthAmericanRegions() / .AddNorthAmericanCities(withinMeters:) / .AddRegionSet(name, IRegionSource), o => { o.UseDelegate<T>(); o.RegionSerializerOptions; })` | `IRegionGeofencesBridge` (`RegionGeofencingJsonContext`): `GetStatusAsync`, `RequestAccessAsync`, `StartAsync`, `StopAsync`, `GetCurrentAsync`, `OnChangeAsync` (`regiongeofence.change`; background.js `regiongeofence`). Backed by Shiny.Geofencing.Regions; region sets are registered in C# only, and the app must not also call `AddRegionGeofencing`. Document sets (`AddDocumentRegionSet`, `AddNorthAmerican*`) need the app to reference Shiny.Geofencing.Regions.DocumentDb and register a SQLite store with the geometry mapped (`MapSpatialProperty` / `MapNorthAmericaData()`, plus `AddNorthAmericaSeeder()` for the dataset). `region`: a geocoder set's placemark always; anything else only through `o.RegionSerializerOptions` metadata (pass the store's `JsonSerializerOptions`), null without it. `start`: 409 `geofence_refused`. Shares Shiny.Gps' listener with the GPS bridge. Android, iOS, Mac Catalyst; 501 elsewhere | | `.BluetoothLE` | `AddBluetoothLEBridge()` | `IBluetoothLEBridge` | | `.Beacons` | `AddBeaconsBridge(BeaconFeatures.All, options)`: pass flags to register only some features; a feature left out, or one the platform reports `NotSupported` (iBeacon ranging and monitoring on macOS), answers 501 | `IBeaconsBridge` (`BeaconsJsonContext`): ranging and Eddystone scans need their event listened to first (`OnBeaconAsync`, `OnEddystoneAsync`), otherwise 409 `not_listening`, and stop with the last listener. Monitoring transitions go to `OnRegionAsync` and the `beacon` native call | | `.Obd` | `AddObdBridge()` | `IObdBridge` | | `.Printers` | `AddPrintersBridge(o => o.NetworkPorts.Add(9200))` — `PrintersBridgeOptions.NetworkPorts` (9100–9102); registers BLE, mDNS and the PDF renderer itself | `IPrintersBridge` (`PrintersJsonContext`) + the `PrintElements` builder — ESC/POS / TSPL receipt printers over BLE or TCP; see Printing below | | `.Printing` | `AddPrintingBridge(o => o.MaxFileBytes = …)` — `PrintingBridgeOptions`: `MaxJsonBytes` (16 MB), `MaxFileBytes` (512 MB) | `IPrintingBridge` (`PrintingJsonContext`) — the OS print dialog / spooler for PDF, image, HTML; see Printing below | | `.Wifi` | `AddWifiBridge(hotspot)` | `IWifiBridge` | | `.Discovery` | `AddDiscoveryBridge(protocols)` | `IDiscoveryBridge` | | `.Push` | `AddPushBridge()` | `IPushBridge` | | `.Wearables` | `AddWearablesBridge(o => o.Folder = "watch")` — `WearablesBridgeOptions`: `Root` (`data`), `Folder` (`wearables`), `RegisterWearableService` (on) | `IWearablesBridge` — the companion Apple Watch / Wear OS app via Shiny.Wearables 5.9; iOS and Android only, `501` elsewhere | | `.LiveActivities` | `AddLiveActivitiesBridge(o => o.ChannelName = "Deliveries")` — Shiny's `LiveActivityOptions`; iOS also needs `<ShinyLiveActivityWidget>true</ShinyLiveActivityWidget>` | `ILiveActivitiesBridge` — iOS Live Activities and Android Live Updates |
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen