Skip to content
Shiny.NET

GPS & Motion

NuGet package Shiny.AppDeviceBridge.Gps

The GPS bridge gives the page permissions, the current and last position, and a listener whose readings arrive as gps.reading events and at background.js. The motion bridge, in the same package, reports walking, running, cycling, driving or stationary, and the geocoding bridge turns a position into an address. Both are backed by Shiny.Gps.

GPS works on Android, iOS, Mac Catalyst and Windows; motion activity on Android, iOS and Mac Catalyst. Everywhere else they answer 501. For the page’s own position, navigator.geolocation with AllowWebPermissions(WebAppWebPermissions.Geolocation) works on every head except Linux, and needs no bridge — see Hosting.

Terminal window
dotnet add package Shiny.AppDeviceBridge.Gps
builder.UseAppDeviceBridge(
bridge => bridge.AddGpsBridge().AddMotionActivityBridge().AddGeocodingBridge(),
webApp => { /* … */ }
);
Client C# TypeScript
IGpsBridge AddGpsBridgeClient() GpsBridge
IMotionBridge AddMotionBridgeClient() MotionBridge
IGeocodingBridge AddGeocodingBridgeClient() GeocodingBridge

Walking, running, cycling, driving or stationary, from the OS’s activity recognition. There’s no history, only the latest reading and live ones. It ships in Shiny.AppDeviceBridge.Gps, but AddGpsBridge() doesn’t include it, because it needs its own setup: NSMotionUsageDescription on iOS (the permission request crashes without it), and ACTIVITY_RECOGNITION with Google Play Services on Android. Other platforms return 501.

GPS and motion listeners keep running without a page, because background.js receives their readings too — see Native Calls & Background.

The addresses at a position, from the platform geocoder: MapKit / CoreLocation on iOS and Mac Catalyst, and android.location.Geocoder on Android. Windows, Linux, macOS and Android devices without a geocoding backend (typically no Google Play services) use OpenStreetMap’s Nominatim instead, so it works on every platform. It needs network access but no location permission, which is why AddGpsBridge() doesn’t include it: add AddGeocodingBridge() when you want it.

The public Nominatim server allows one request a second and asks for a User-Agent that identifies your app; it defaults to your entry assembly’s name and version. To use your own server, or another User-Agent, register the geocoder first, and the bridge keeps it:

builder.Services.AddGeocoding(o => o.BaseUri = new Uri("https://nominatim.example.com/"));

Display OpenStreetMap’s attribution wherever Nominatim results appear.

import { GeocodingBridge } from "@shinyorg/appdevicebridge";
const [place] = await new GeocodingBridge().reverseGeocode(43.6426, -79.3871);
console.log(place?.formattedAddress); // most relevant first; empty when nothing was found
import { GpsBridge } from "@shinyorg/appdevicebridge";
const gps = new GpsBridge();
const settings = { backgroundMode: "None", requestPreciseAccuracy: true, autoRestart: true } as const;
const { access } = await gps.requestAccess(settings);
if (access !== "Available") return;
const here = await gps.getCurrentPosition(); // one fix, or null
await gps.onReading(r => console.log(r.latitude, r.longitude, `±${r.positionAccuracy} m`));
await gps.startListener(settings); // 409 when another listener runs

Only one listener runs at a time, and it belongs to the app, not the page — getListener() says what’s running.

Gps/_bridge/gps7 requests · 1 events

GPS: permission, the last and current position, and a listener whose readings arrive as events. Android, iOS, Mac Catalyst and Windows; elsewhere every call fails with 501.

GET/_bridge/gps/status#

The permission state for a kind of GPS use, without prompting.

C#IGpsBridge.GetStatusAsyncTSGpsBridge.getStatus
ParameterInType
modequeryGpsAccessModeoptional
Response
200 GpsAccessResult
GpsAccessResult · 1 field
accessAccessState
POST/_bridge/gps/access#

Requests the permission the listener settings need — background access for a background mode.

C#IGpsBridge.RequestAccessAsyncTSGpsBridge.requestAccess
Body
application/json · GpsListenerSettings
GpsListenerSettings · 3 fields
backgroundModeGpsBackgroundMode
requestPreciseAccuracybooleanAsk for precise rather than approximate location where the platform distinguishes.
autoRestartbooleanRestart the listener after the app is relaunched.
Response
200 GpsAccessResult
GpsAccessResult · 1 field
accessAccessState
GET/_bridge/gps/last#

The last known position, or null when there is none.

C#IGpsBridge.GetLastReadingAsyncTSGpsBridge.getLastReading
Response
200 GpsReading · or 204 when there is none
GpsReading · 11 fields
latitudenumber
longitudenumber
positionAccuracynumberMeters.
timestampstring
headingnumberDegrees from north.
headingAccuracynumber
altitudenumberMeters.
speednumberMeters per second.
speedAccuracynumber
floornumber
isStationaryboolean
GET/_bridge/gps/current#

A fresh position, or null when none could be had.

C#IGpsBridge.GetCurrentPositionAsyncTSGpsBridge.getCurrentPosition
Response
200 GpsReading · or 204 when there is none
GpsReading · 11 fields
latitudenumber
longitudenumber
positionAccuracynumberMeters.
timestampstring
headingnumberDegrees from north.
headingAccuracynumber
altitudenumberMeters.
speednumberMeters per second.
speedAccuracynumber
floornumber
isStationaryboolean
GET/_bridge/gps/listener#

The running listener's settings, or null when not listening.

C#IGpsBridge.GetListenerAsyncTSGpsBridge.getListener
Response
200 GpsListenerSettings · or 204 when there is none
GpsListenerSettings · 3 fields
backgroundModeGpsBackgroundMode
requestPreciseAccuracybooleanAsk for precise rather than approximate location where the platform distinguishes.
autoRestartbooleanRestart the listener after the app is relaunched.
POST/_bridge/gps/listener#

Starts listening; readings arrive through onReading, and in the background to the web app's gps handler. Fails with 409 without permission or while another listener runs.

C#IGpsBridge.StartListenerAsyncTSGpsBridge.startListener
Body
application/json · GpsListenerSettings
GpsListenerSettings · 3 fields
backgroundModeGpsBackgroundMode
requestPreciseAccuracybooleanAsk for precise rather than approximate location where the platform distinguishes.
autoRestartbooleanRestart the listener after the app is relaunched.
Response
204 No content
Errors
409Conflict
DELETE/_bridge/gps/listener#

Stops listening.

C#IGpsBridge.StopListenerAsyncTSGpsBridge.stopListener
Response
204 No content
Motion/_bridge/motion6 requests · 1 events

Motion activity: walking, running, cycling, driving or stationary, as the OS's activity recognition reports it. Android, iOS and Mac Catalyst; elsewhere every call fails with 501. There is no history, only the latest reading and live ones.

GET/_bridge/motion/status#

The permission state, without prompting.

C#IMotionBridge.GetStatusAsyncTSMotionBridge.getStatus
Response
200 MotionAccessResult
MotionAccessResult · 1 field
accessAccessState
POST/_bridge/motion/access#

Requests motion activity permission.

C#IMotionBridge.RequestAccessAsyncTSMotionBridge.requestAccess
Response
200 MotionAccessResult
MotionAccessResult · 1 field
accessAccessState
GET/_bridge/motion/current#

The latest reading, or null when there is none.

C#IMotionBridge.GetCurrentAsyncTSMotionBridge.getCurrent
Response
200 MotionActivity · or 204 when there is none
MotionActivity · 3 fields
activityMotionActivityType
confidenceMotionActivityConfidence
timestampstring
GET/_bridge/motion/listener#

Whether the listener is running.

C#IMotionBridge.GetListenerAsyncTSMotionBridge.getListener
Response
200 MotionListener
MotionListener · 1 field
isListeningboolean
POST/_bridge/motion/listener#

Starts listening; readings arrive through onActivity, and in the background to the web app's motion handler. Fails with 409 without permission.

C#IMotionBridge.StartListenerAsyncTSMotionBridge.startListener
Response
204 No content
Errors
409Conflict
DELETE/_bridge/motion/listener#

Stops listening.

C#IMotionBridge.StopListenerAsyncTSMotionBridge.stopListener
Response
204 No content
Geocoding/_bridge/geocoding1 requests · 0 events

Reverse geocoding: the addresses at a position, from the platform geocoder (MapKit / CoreLocation on iOS and Mac Catalyst, android.location.Geocoder on Android) or, on Windows, Linux, macOS and Android devices without a geocoding backend, OpenStreetMap's Nominatim. It needs network access but no location permission.

GET/_bridge/geocoding/reverse#

The placemarks at a position, most relevant first — empty when the geocoder found nothing. Fails with 400 for a position off the map and 503 when the platform geocoder could not answer (typically no network).

C#IGeocodingBridge.ReverseGeocodeAsyncTSGeocodingBridge.reverseGeocode
ParameterInType
latitudequerynumberrequired
longitudequerynumberrequired
Response
200 Placemark[]
Placemark · 13 fields
latitudenumberThe latitude of the position the placemark describes.
longitudenumberThe longitude of the position the placemark describes.
namestring | nullThe name of the place: a landmark or business, or the street address.
subThoroughfarestring | nullThe street number.
thoroughfarestring | nullThe street name.
subLocalitystring | nullThe neighbourhood or district.
localitystring | nullThe city or town.
subAdministrativeAreastring | nullThe county or equivalent.
administrativeAreastring | nullThe state, province or equivalent.
postalCodestring | nullThe postal or zip code.
countryCodestring | nullThe ISO 3166-1 alpha-2 country code.
countryNamestring | nullThe localized country name.
formattedAddressstring | nullThe full address on one line, formatted by the platform.
Errors
400Bad request503Unavailable
Gps1 event
EVENTgps.reading#

A reading from the listener, while the page is open.

C#IGpsBridge.OnReadingAsyncTSGpsBridge.onReading
Payload
GpsReading
latitudenumber
longitudenumber
positionAccuracynumberMeters.
timestampstring
headingnumberDegrees from north.
headingAccuracynumber
altitudenumberMeters.
speednumberMeters per second.
speedAccuracynumber
floornumber
isStationaryboolean
Motion1 event

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.

GpsAccessModeenum#

Which kind of GPS use a permission check is for.

"Foreground""Background""Realtime"
AccessStateenum#

Where a permission stands. Reported by every bridge that needs one; the names match Shiny's own.

"Unknown""NotSupported""NotSetup""Disabled""Restricted""Denied""Available"
GpsListenerSettingsobject#
backgroundModeGpsBackgroundMode
requestPreciseAccuracybooleanAsk for precise rather than approximate location where the platform distinguishes.
autoRestartbooleanRestart the listener after the app is relaunched.
GpsBackgroundModeenum#

How a listener keeps running with the app in the background.

"None""Standard""Realtime"
GpsReadingobject#

A position.

latitudenumber
longitudenumber
positionAccuracynumberMeters.
timestampstring
headingnumberDegrees from north.
headingAccuracynumber
altitudenumberMeters.
speednumberMeters per second.
speedAccuracynumber
floornumber
isStationaryboolean
MotionActivityTypeenum#
"Unknown""Stationary""Walking""Running""Cycling""Automotive"
MotionActivityConfidenceenum#
"Low""Medium""High"
MotionListenerobject#
isListeningboolean
Placemarkobject#

A human-readable description of a position from the platform geocoder. Any part it could not resolve is null.

latitudenumberThe latitude of the position the placemark describes.
longitudenumberThe longitude of the position the placemark describes.
namestring | nullThe name of the place: a landmark or business, or the street address.
subThoroughfarestring | nullThe street number.
thoroughfarestring | nullThe street name.
subLocalitystring | nullThe neighbourhood or district.
localitystring | nullThe city or town.
subAdministrativeAreastring | nullThe county or equivalent.
administrativeAreastring | nullThe state, province or equivalent.
postalCodestring | nullThe postal or zip code.
countryCodestring | nullThe ISO 3166-1 alpha-2 country code.
countryNamestring | nullThe localized country name.
formattedAddressstring | nullThe full address on one line, formatted by the platform.

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