Skip to content
Shiny.NET

App & Device

Two bridges give the page the device and the app around it:

  • /_bridge/app, in Shiny.AppDeviceBridge itself, over Shiny.Core: device info, orientation, the screen, the app’s settings, app store lookups and reviews (Shiny.AppStore), launching at login, battery and connectivity. No MAUI, so it runs on a headless device too.
  • /_bridge/device, in Shiny.AppDeviceBridge.Maui, over .NET MAUI Essentials: the browser, maps, the share sheet, haptics and vibration, keeping the screen awake, and the clipboard.

The same Maui package adds the sensors bridge.

NuGet package Shiny.AppDeviceBridge.Maui
Terminal window
dotnet add package Shiny.AppDeviceBridge.Maui
builder.UseAppDeviceBridge(
bridge => bridge
.AddAppBridge()
.AddDeviceBridge(),
webApp => { /* … */ }
);

AddAppBridge takes the app store’s identifiers and the launch-at-login options. The store endpoints answer 501 until you pass the first:

bridge.AddAppBridge(
store => store.AppleAppId = "123456789", // Shiny.AppStore's AppStoreOptions
startup => startup.Arguments.Add("--autostart") // Shiny.Core's AppStartupOptions
);
Client C# TypeScript
IAppBridge AddAppBridgeClient() AppBridge
IDeviceBridge AddDeviceBridgeClient() DeviceBridge

Both clients are in Shiny.AppDeviceBridge.Client, which every page already references.

Device info, culture and time zone come from Shiny’s IPlatform; the screen and orientation from IScreenInfo. Both are registered by every Shiny host, and AddAppBridge registers Shiny’s core services itself where no host has. GET app/info includes applicationIdentifier: the bundle identifier on Apple, the package name on Android, the package identity name on a packaged Windows app, or the Flatpak app id or Snap name on Linux — null when the app has none.

GET app/screen is the main display in physical pixels, with its density, orientation and refresh rate; app.screen fires when any of it changes, and app.orientation when the app turns between portrait and landscape. POST app/orientation locks the orientation and answers { "success": false } where the platform cannot (iOS before 16, Mac Catalyst, macOS, Linux). On iOS, holding the lock needs Shiny’s IosOrientationLock in the app delegate.

Battery and connectivity come from Shiny.Core’s IBattery and IConnectivity, which UseAppDeviceBridge registers. app.battery carries the charge, state, power source and energy saver in one event. Every head has both; the Linux (GTK4) head reads the battery from sysfs (/sys/class/power_supply). A host without MAUI calls services.AddBattery() and services.AddConnectivity() itself — plain .NET has a battery on Linux only, so elsewhere GET app/battery answers 501.

The store endpoints go through Shiny.AppStore’s IAppStore: the Apple App Store and Mac App Store, Google Play, the Microsoft Store (which needs WindowsProductId), and Flatpak or Snap on Linux. GET app/store answers 404 when the store has no listing. POST app/store/review asks for the platform’s in-app review prompt, which the OS may decline to show; POST app/store/review-page opens the store’s review page instead.

GET /_bridge/app/startup says whether the app launches when the user logs in, through Shiny.Core’s IAppStartupInstaller. Windows writes it under HKCU\…\CurrentVersion\Run (unpackaged apps only — the OS virtualizes that key for MSIX), macOS 13+ submits the running bundle to SMAppService, and Linux writes ~/.config/autostart/{Identifier}.desktop or a systemd unit. Mobile has no such list, so it answers { "supported": false, "state": "NotSupported" } and the rest return 501. state is read back from the OS every time rather than remembered, because the user can turn a registered app off in Task Manager, System Settings or Login Items without the app hearing about it — which is also why Enabled is not the only success: DisabledByUser, DisabledByPolicy and RequiresApproval mean the user has to finish the job in the OS, and POST app/startup/settings opens the screen where they do. The OS refusing a registration answers 409 startup_failed.

Everything here comes from .NET MAUI Essentials, so each head’s own build decides what works. A feature a backend lacks returns 501 on its own, and the rest keep working.

POST device/browser opens an http or https page in an in-app browser sheet, or the default browser with "launchMode": "External"; mailto and tel go to the app that handles them. Other schemes answer 400. POST device/map opens the maps app at a location, with directions when navigationMode is set.

Files are shared by the same { root, path } as the files bridge, at most 20 at once:

await new DeviceBridge().share({ files: [{ root: "data", path: "photos/cat.jpg" }] });

vibrate is capped at 5 seconds, and on Android needs VIBRATE in the manifest. PUT device/keep-awake answers 501 where the screen cannot be kept on, rather than succeeding without doing it. The clipboard holds text, at most 1 MB.

App/_bridge/app15 requests · 6 events

The app and the device it runs on, over Shiny.Core: information, orientation, the screen, settings, the app store, launch at login, battery and connectivity. Each feature fails with 501 on its own where the platform lacks it.

GET/_bridge/app/info#

The app's version and the device, platform, orientation, culture and time zone.

C#IAppBridge.GetInfoAsyncTSAppBridge.getInfo
Response
200 AppInfo
AppInfo · 10 fields
appVersionstring
deviceManufacturerstring
deviceModelstring
platformVersionstring | null
platformstringAndroid, iOS, MacCatalyst, macOS, Windows or Linux.
deviceIdiomstringUnknown, Phone, Tablet, Desktop, TV, Watch or Car.
orientationDisplayOrientation
culturestringA culture name such as en-US.
timeZonestringA time zone id.
applicationIdentifierstring | nullThe bundle identifier on Apple, the package name on Android, the package identity name on a packaged Windows app, the Flatpak app id or Snap name on Linux — null when the app has no such identity.
POST/_bridge/app/orientation#

Turns the app to an orientation and holds it there. False where the platform cannot.

C#IAppBridge.SetOrientationAsyncTSAppBridge.setOrientation
Body
application/json · OrientationRequest
OrientationRequest · 1 field
orientationDisplayOrientation
Response
200 AppActionResult
AppActionResult · 1 field
successbooleanFalse when the platform could not do it.
DELETE/_bridge/app/orientation#

Unlocks the orientation.

C#IAppBridge.ResetOrientationAsyncTSAppBridge.resetOrientation
Response
200 AppActionResult
AppActionResult · 1 field
successbooleanFalse when the platform could not do it.
GET/_bridge/app/screen#

The main display.

C#IAppBridge.GetScreenAsyncTSAppBridge.getScreen
Response
200 ScreenInfo
ScreenInfo · 5 fields
widthnumberPhysical pixels, as currently oriented.
heightnumberPhysical pixels, as currently oriented.
densitynumberPhysical pixels per device-independent unit.
orientationDisplayOrientation
refreshRatenumberHertz, or 0 where the platform does not report it.
POST/_bridge/app/settings#

Opens the app's page in the OS settings, or the system settings where there is no such page.

C#IAppBridge.OpenSettingsAsyncTSAppBridge.openSettings
Response
204 No content
GET/_bridge/app/store#

The app's store listing. Fails with 501 unless the app registered the store, 404 when it is not listed.

C#IAppBridge.GetStoreListingAsyncTSAppBridge.getStoreListing
Response
200 AppStoreListing
AppStoreListing · 9 fields
storeVersionstring
currentVersionstring
needsUpdateboolean
storeUrlstring
releaseNotesstring | null
releasedAtstring | null
averageRatingnumber | null
ratingCountnumber | null
minimumOsVersionstring | null
Errors
404Not found501Not on this platform
POST/_bridge/app/store/open#

Opens the app's store listing.

C#IAppBridge.OpenStoreAsyncTSAppBridge.openStore
Response
200 AppActionResult
AppActionResult · 1 field
successbooleanFalse when the platform could not do it.
POST/_bridge/app/store/review#

Asks the OS to show its review prompt, which it may decline to do.

C#IAppBridge.RequestReviewAsyncTSAppBridge.requestReview
Response
200 AppActionResult
AppActionResult · 1 field
successbooleanFalse when the platform could not do it.
POST/_bridge/app/store/review-page#

Opens the store listing on its reviews, where the store has such a page.

C#IAppBridge.OpenReviewPageAsyncTSAppBridge.openReviewPage
Response
200 AppActionResult
AppActionResult · 1 field
successbooleanFalse when the platform could not do it.
GET/_bridge/app/startup#

Whether the app launches at login. Always answers, so a page can decide whether to show the option.

C#IAppBridge.GetStartupAsyncTSAppBridge.getStartup
Response
200 StartupStatus
StartupStatus · 2 fields
supportedbooleanWhether this platform has a launch-at-login list.
stateStartupStateRead back from the OS every time: the user can turn it off there.
POST/_bridge/app/startup/registration#

Launches the app at login. A state of RequiresApproval or disabled did not fail: the user finishes it in the OS, which openStartupSettings opens. Fails with 409 when the OS refuses.

C#IAppBridge.RegisterStartupAsyncTSAppBridge.registerStartup
Response
200 StartupStatus
StartupStatus · 2 fields
supportedbooleanWhether this platform has a launch-at-login list.
stateStartupStateRead back from the OS every time: the user can turn it off there.
Errors
409Conflict
DELETE/_bridge/app/startup/registration#

Stops launching the app at login.

C#IAppBridge.UnregisterStartupAsyncTSAppBridge.unregisterStartup
Response
200 StartupStatus
StartupStatus · 2 fields
supportedbooleanWhether this platform has a launch-at-login list.
stateStartupStateRead back from the OS every time: the user can turn it off there.
POST/_bridge/app/startup/settings#

Opens the OS's login items screen. opened is false where there is none.

C#IAppBridge.OpenStartupSettingsAsyncTSAppBridge.openStartupSettings
Response
200 StartupSettingsResult
StartupSettingsResult · 1 field
openedboolean
GET/_bridge/app/connectivity#

Network access and the kinds of connection in use.

C#IAppBridge.GetConnectivityAsyncTSAppBridge.getConnectivity
Response
200 ConnectivityInfo
ConnectivityInfo · 2 fields
networkAccessNetworkAccess
connectionProfilesConnectionProfile[]
Device/_bridge/device12 requests · 0 events

The device's apps and feedback, over .NET MAUI Essentials: the browser, maps, the share sheet, haptics, vibration, keeping the screen awake and the clipboard. Each feature fails with 501 on its own where the platform lacks it.

POST/_bridge/device/browser#

Opens an http, https, mailto or tel URI — in an in-app browser sheet, or the default browser with External.

C#IDeviceBridge.OpenBrowserAsyncTSDeviceBridge.openBrowser
Body
application/json · OpenBrowserRequest
OpenBrowserRequest · 3 fields
uristringAn http, https, mailto or tel URI.
showTitleboolean
launchModeBrowserLaunchMode
Response
200 DeviceActionResult
DeviceActionResult · 1 field
successbooleanFalse when the platform could not do it.
POST/_bridge/device/map#

Opens the maps app at a location, optionally with directions.

C#IDeviceBridge.OpenMapAsyncTSDeviceBridge.openMap
Body
application/json · OpenMapRequest
OpenMapRequest · 3 fields
latitudenumber
longitudenumber
navigationModeNavigationMode
Response
200 DeviceActionResult
DeviceActionResult · 1 field
successbooleanFalse when the platform could not do it.
POST/_bridge/device/share#

Opens the share sheet for text and a link, or for files in the page's file roots.

C#IDeviceBridge.ShareAsyncTSDeviceBridge.share
Body
application/json · ShareRequest
ShareRequest · 5 fields
textstring | null
uristring | nullAn absolute http or https URI.
titlestring | null
subjectstring | null
filesBridgeFile[] | null
Response
204 No content
POST/_bridge/device/haptics#

Performs haptic feedback.

C#IDeviceBridge.PerformHapticsAsyncTSDeviceBridge.performHaptics
Body
application/json · HapticsRequest
HapticsRequest · 1 field
typeHapticFeedbackType
Response
204 No content
POST/_bridge/device/vibrate#

Vibrates, for at most 5 seconds.

C#IDeviceBridge.VibrateAsyncTSDeviceBridge.vibrate
Body
application/json · VibrateRequest
VibrateRequest · 1 field
durationMsnumber | nullHalf a second when null; at most 5,000.
Response
204 No content
DELETE/_bridge/device/vibrate#

Stops vibrating.

C#IDeviceBridge.CancelVibrationAsyncTSDeviceBridge.cancelVibration
Response
204 No content
GET/_bridge/device/keep-awake#

Whether the screen is being kept on.

C#IDeviceBridge.GetKeepAwakeAsyncTSDeviceBridge.getKeepAwake
Response
200 KeepAwakeStatus
KeepAwakeStatus · 1 field
keepAwakeboolean
PUT/_bridge/device/keep-awake#

Keeps the screen on.

C#IDeviceBridge.KeepScreenAwakeAsyncTSDeviceBridge.keepScreenAwake
Response
204 No content
DELETE/_bridge/device/keep-awake#

Lets the screen sleep again.

C#IDeviceBridge.AllowScreenSleepAsyncTSDeviceBridge.allowScreenSleep
Response
204 No content
GET/_bridge/device/clipboard#

The clipboard's text, or null text when it holds none.

C#IDeviceBridge.GetClipboardAsyncTSDeviceBridge.getClipboard
Response
200 ClipboardText
ClipboardText · 1 field
textstring | null
PUT/_bridge/device/clipboard#

Puts text on the clipboard, at most 1 MB.

C#IDeviceBridge.SetClipboardAsyncTSDeviceBridge.setClipboard
Body
application/json · ClipboardText
ClipboardText · 1 field
textstring | null
Response
204 No content
DELETE/_bridge/device/clipboard#

Clears the clipboard.

C#IDeviceBridge.ClearClipboardAsyncTSDeviceBridge.clearClipboard
Response
204 No content
EVENTapp.orientation#

The app turned between portrait and landscape.

C#IAppBridge.OnOrientationChangedAsyncTSAppBridge.onOrientationChanged
Payload
OrientationChanged
orientationDisplayOrientation
EVENTapp.screen#

The main display changed: a rotation, a resolution or scale change, or another display.

C#IAppBridge.OnScreenChangedAsyncTSAppBridge.onScreenChanged
Payload
ScreenInfo
widthnumberPhysical pixels, as currently oriented.
heightnumberPhysical pixels, as currently oriented.
densitynumberPhysical pixels per device-independent unit.
orientationDisplayOrientation
refreshRatenumberHertz, or 0 where the platform does not report it.
EVENTapp.culture#

The device's culture changed.

C#IAppBridge.OnCultureChangedAsyncTSAppBridge.onCultureChanged
Payload
CultureChanged
culturestring
EVENTapp.timezone#

The device's time zone changed.

C#IAppBridge.OnTimeZoneChangedAsyncTSAppBridge.onTimeZoneChanged
Payload
TimeZoneChanged
timeZonestring

JSON property names are camelCase and enums travel as their names. A ? marks a property the payload may omit;| null one that may be null.

AppInfoobject#
appVersionstring
deviceManufacturerstring
deviceModelstring
platformVersionstring | null
platformstringAndroid, iOS, MacCatalyst, macOS, Windows or Linux.
deviceIdiomstringUnknown, Phone, Tablet, Desktop, TV, Watch or Car.
orientationDisplayOrientation
culturestringA culture name such as en-US.
timeZonestringA time zone id.
applicationIdentifierstring | nullThe bundle identifier on Apple, the package name on Android, the package identity name on a packaged Windows app, the Flatpak app id or Snap name on Linux — null when the app has no such identity.
DisplayOrientationenum#
"Unknown""Portrait""Landscape"
AppActionResultobject#
successbooleanFalse when the platform could not do it.
ScreenInfoobject#
widthnumberPhysical pixels, as currently oriented.
heightnumberPhysical pixels, as currently oriented.
densitynumberPhysical pixels per device-independent unit.
orientationDisplayOrientation
refreshRatenumberHertz, or 0 where the platform does not report it.
AppStoreListingobject#
storeVersionstring
currentVersionstring
needsUpdateboolean
storeUrlstring
releaseNotesstring | null
releasedAtstring | null
averageRatingnumber | null
ratingCountnumber | null
minimumOsVersionstring | null
StartupStatusobject#
supportedbooleanWhether this platform has a launch-at-login list.
stateStartupStateRead back from the OS every time: the user can turn it off there.
StartupStateenum#
"NotSupported""NotRegistered""Enabled""DisabledByUser""DisabledByPolicy""RequiresApproval"
StartupSettingsResultobject#
openedboolean
BatteryStateenum#
"Unknown""Charging""Discharging""Full""NotCharging""NotPresent"
BatteryPowerSourceenum#
"Unknown""Battery""AC""Usb""Wireless"
EnergySaverStatusenum#
"Unknown""On""Off"
NetworkAccessenum#
"Unknown""None""Local""ConstrainedInternet""Internet"
ConnectionProfileenum#
"Unknown""Bluetooth""Cellular""Ethernet""WiFi"
CultureChangedobject#
culturestring
TimeZoneChangedobject#
timeZonestring
OpenBrowserRequestobject#
uristringAn http, https, mailto or tel URI.
showTitle?boolean
launchMode?BrowserLaunchMode
BrowserLaunchModeenum#
"SystemPreferred""External"
DeviceActionResultobject#
successbooleanFalse when the platform could not do it.
OpenMapRequestobject#
latitudenumber
longitudenumber
navigationMode?NavigationMode
NavigationModeenum#
"None""Default""Bicycling""Driving""Transit""Walking"
ShareRequestobject#

What to share: text and uri, or files from the page's file roots — at most 20.

text?string | null
uri?string | nullAn absolute http or https URI.
title?string | null
subject?string | null
files?BridgeFile[] | null
BridgeFileobject#

A file the page can reach through the files bridge: a named root and a path inside it. Bridges that take or return a file use this, so nothing reaches the page that /_bridge/files would not also serve.

rootstring
pathstring
HapticFeedbackTypeenum#
"Click""LongPress"
VibrateRequestobject#
durationMs?number | nullHalf a second when null; at most 5,000.
KeepAwakeStatusobject#
keepAwakeboolean
ClipboardTextobject#
textstring | null

These routes sit under AppDeviceBridgePolicies.Bridges like every other bridge: callers on this device by default. See Security.