@mentra/miniapp 0.3.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +259 -0
- package/dist/background/index.d.ts +60 -0
- package/dist/background/index.d.ts.map +1 -0
- package/dist/background/index.js +25 -0
- package/dist/background/index.js.map +1 -0
- package/dist/background/register.d.ts +38 -0
- package/dist/background/register.d.ts.map +1 -0
- package/dist/background/register.js +75 -0
- package/dist/background/register.js.map +1 -0
- package/dist/dev-reload.d.ts +21 -0
- package/dist/dev-reload.d.ts.map +1 -0
- package/dist/dev-reload.js +62 -0
- package/dist/dev-reload.js.map +1 -0
- package/dist/envelope.d.ts +23 -0
- package/dist/envelope.d.ts.map +1 -0
- package/dist/envelope.js +47 -0
- package/dist/envelope.js.map +1 -0
- package/dist/globals.d.ts +54 -0
- package/dist/globals.d.ts.map +1 -0
- package/dist/globals.js +12 -0
- package/dist/globals.js.map +1 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/modules/actions.d.ts +54 -0
- package/dist/modules/actions.d.ts.map +1 -0
- package/dist/modules/actions.js +122 -0
- package/dist/modules/actions.js.map +1 -0
- package/dist/modules/auth.d.ts +25 -0
- package/dist/modules/auth.d.ts.map +1 -0
- package/dist/modules/auth.js +52 -0
- package/dist/modules/auth.js.map +1 -0
- package/dist/modules/base64.d.ts +16 -0
- package/dist/modules/base64.d.ts.map +1 -0
- package/dist/modules/base64.js +92 -0
- package/dist/modules/base64.js.map +1 -0
- package/dist/modules/blob.d.ts +161 -0
- package/dist/modules/blob.d.ts.map +1 -0
- package/dist/modules/blob.js +277 -0
- package/dist/modules/blob.js.map +1 -0
- package/dist/modules/camera.d.ts +170 -0
- package/dist/modules/camera.d.ts.map +1 -0
- package/dist/modules/camera.js +116 -0
- package/dist/modules/camera.js.map +1 -0
- package/dist/modules/cloud.d.ts +34 -0
- package/dist/modules/cloud.d.ts.map +1 -0
- package/dist/modules/cloud.js +70 -0
- package/dist/modules/cloud.js.map +1 -0
- package/dist/modules/dashboard.d.ts +16 -0
- package/dist/modules/dashboard.d.ts.map +1 -0
- package/dist/modules/dashboard.js +27 -0
- package/dist/modules/dashboard.js.map +1 -0
- package/dist/modules/display.d.ts +106 -0
- package/dist/modules/display.d.ts.map +1 -0
- package/dist/modules/display.js +57 -0
- package/dist/modules/display.js.map +1 -0
- package/dist/modules/events.d.ts +160 -0
- package/dist/modules/events.d.ts.map +1 -0
- package/dist/modules/events.js +168 -0
- package/dist/modules/events.js.map +1 -0
- package/dist/modules/glasses.d.ts +28 -0
- package/dist/modules/glasses.d.ts.map +1 -0
- package/dist/modules/glasses.js +39 -0
- package/dist/modules/glasses.js.map +1 -0
- package/dist/modules/heading.d.ts +16 -0
- package/dist/modules/heading.d.ts.map +1 -0
- package/dist/modules/heading.js +20 -0
- package/dist/modules/heading.js.map +1 -0
- package/dist/modules/imu.d.ts +31 -0
- package/dist/modules/imu.d.ts.map +1 -0
- package/dist/modules/imu.js +40 -0
- package/dist/modules/imu.js.map +1 -0
- package/dist/modules/input.d.ts +34 -0
- package/dist/modules/input.d.ts.map +1 -0
- package/dist/modules/input.js +50 -0
- package/dist/modules/input.js.map +1 -0
- package/dist/modules/led.d.ts +35 -0
- package/dist/modules/led.d.ts.map +1 -0
- package/dist/modules/led.js +39 -0
- package/dist/modules/led.js.map +1 -0
- package/dist/modules/location.d.ts +27 -0
- package/dist/modules/location.d.ts.map +1 -0
- package/dist/modules/location.js +35 -0
- package/dist/modules/location.js.map +1 -0
- package/dist/modules/mic.d.ts +44 -0
- package/dist/modules/mic.d.ts.map +1 -0
- package/dist/modules/mic.js +67 -0
- package/dist/modules/mic.js.map +1 -0
- package/dist/modules/miniapps.d.ts +72 -0
- package/dist/modules/miniapps.d.ts.map +1 -0
- package/dist/modules/miniapps.js +48 -0
- package/dist/modules/miniapps.js.map +1 -0
- package/dist/modules/navigation.d.ts +326 -0
- package/dist/modules/navigation.d.ts.map +1 -0
- package/dist/modules/navigation.js +386 -0
- package/dist/modules/navigation.js.map +1 -0
- package/dist/modules/permissions.d.ts +58 -0
- package/dist/modules/permissions.d.ts.map +1 -0
- package/dist/modules/permissions.js +66 -0
- package/dist/modules/permissions.js.map +1 -0
- package/dist/modules/phone.d.ts +95 -0
- package/dist/modules/phone.d.ts.map +1 -0
- package/dist/modules/phone.js +110 -0
- package/dist/modules/phone.js.map +1 -0
- package/dist/modules/pivots/engine.d.ts +169 -0
- package/dist/modules/pivots/engine.d.ts.map +1 -0
- package/dist/modules/pivots/engine.js +779 -0
- package/dist/modules/pivots/engine.js.map +1 -0
- package/dist/modules/pivots/geometry.d.ts +84 -0
- package/dist/modules/pivots/geometry.d.ts.map +1 -0
- package/dist/modules/pivots/geometry.js +371 -0
- package/dist/modules/pivots/geometry.js.map +1 -0
- package/dist/modules/pivots/instructions.d.ts +139 -0
- package/dist/modules/pivots/instructions.d.ts.map +1 -0
- package/dist/modules/pivots/instructions.js +280 -0
- package/dist/modules/pivots/instructions.js.map +1 -0
- package/dist/modules/speaker.d.ts +169 -0
- package/dist/modules/speaker.d.ts.map +1 -0
- package/dist/modules/speaker.js +251 -0
- package/dist/modules/speaker.js.map +1 -0
- package/dist/modules/storage.d.ts +50 -0
- package/dist/modules/storage.d.ts.map +1 -0
- package/dist/modules/storage.js +99 -0
- package/dist/modules/storage.js.map +1 -0
- package/dist/modules/stream.d.ts +129 -0
- package/dist/modules/stream.d.ts.map +1 -0
- package/dist/modules/stream.js +52 -0
- package/dist/modules/stream.js.map +1 -0
- package/dist/modules/system.d.ts +45 -0
- package/dist/modules/system.d.ts.map +1 -0
- package/dist/modules/system.js +42 -0
- package/dist/modules/system.js.map +1 -0
- package/dist/modules/transcription.d.ts +75 -0
- package/dist/modules/transcription.d.ts.map +1 -0
- package/dist/modules/transcription.js +106 -0
- package/dist/modules/transcription.js.map +1 -0
- package/dist/modules/translation.d.ts +59 -0
- package/dist/modules/translation.d.ts.map +1 -0
- package/dist/modules/translation.js +119 -0
- package/dist/modules/translation.js.map +1 -0
- package/dist/modules/ui.d.ts +186 -0
- package/dist/modules/ui.d.ts.map +1 -0
- package/dist/modules/ui.js +293 -0
- package/dist/modules/ui.js.map +1 -0
- package/dist/protocol.d.ts +299 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +325 -0
- package/dist/protocol.js.map +1 -0
- package/dist/react/MentraProvider.d.ts +33 -0
- package/dist/react/MentraProvider.d.ts.map +1 -0
- package/dist/react/MentraProvider.js +15 -0
- package/dist/react/MentraProvider.js.map +1 -0
- package/dist/react/MiniappHeader.d.ts +44 -0
- package/dist/react/MiniappHeader.d.ts.map +1 -0
- package/dist/react/MiniappHeader.js +32 -0
- package/dist/react/MiniappHeader.js.map +1 -0
- package/dist/react/index.d.ts +13 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +9 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/useCapabilities.d.ts +26 -0
- package/dist/react/useCapabilities.d.ts.map +1 -0
- package/dist/react/useCapabilities.js +27 -0
- package/dist/react/useCapabilities.js.map +1 -0
- package/dist/react/useCapsuleHeaderStyle.d.ts +27 -0
- package/dist/react/useCapsuleHeaderStyle.d.ts.map +1 -0
- package/dist/react/useCapsuleHeaderStyle.js +36 -0
- package/dist/react/useCapsuleHeaderStyle.js.map +1 -0
- package/dist/react/useColorScheme.d.ts +18 -0
- package/dist/react/useColorScheme.d.ts.map +1 -0
- package/dist/react/useColorScheme.js +24 -0
- package/dist/react/useColorScheme.js.map +1 -0
- package/dist/react/useConnected.d.ts +17 -0
- package/dist/react/useConnected.d.ts.map +1 -0
- package/dist/react/useConnected.js +39 -0
- package/dist/react/useConnected.js.map +1 -0
- package/dist/react/useRpc.d.ts +29 -0
- package/dist/react/useRpc.d.ts.map +1 -0
- package/dist/react/useRpc.js +82 -0
- package/dist/react/useRpc.js.map +1 -0
- package/dist/react/useSafeArea.d.ts +21 -0
- package/dist/react/useSafeArea.d.ts.map +1 -0
- package/dist/react/useSafeArea.js +22 -0
- package/dist/react/useSafeArea.js.map +1 -0
- package/dist/session.d.ts +312 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +653 -0
- package/dist/session.js.map +1 -0
- package/dist/sub-path-types.test-d.d.ts +28 -0
- package/dist/sub-path-types.test-d.d.ts.map +1 -0
- package/dist/sub-path-types.test-d.js +37 -0
- package/dist/sub-path-types.test-d.js.map +1 -0
- package/dist/transport/auto.d.ts +35 -0
- package/dist/transport/auto.d.ts.map +1 -0
- package/dist/transport/auto.js +125 -0
- package/dist/transport/auto.js.map +1 -0
- package/dist/transport/dispatch.d.ts +43 -0
- package/dist/transport/dispatch.d.ts.map +1 -0
- package/dist/transport/dispatch.js +91 -0
- package/dist/transport/dispatch.js.map +1 -0
- package/dist/transport/local-socket.d.ts +28 -0
- package/dist/transport/local-socket.d.ts.map +1 -0
- package/dist/transport/local-socket.js +80 -0
- package/dist/transport/local-socket.js.map +1 -0
- package/dist/transport/mock.d.ts +54 -0
- package/dist/transport/mock.d.ts.map +1 -0
- package/dist/transport/mock.js +207 -0
- package/dist/transport/mock.js.map +1 -0
- package/dist/transport/postmessage.d.ts +36 -0
- package/dist/transport/postmessage.d.ts.map +1 -0
- package/dist/transport/postmessage.js +78 -0
- package/dist/transport/postmessage.js.map +1 -0
- package/dist/transport/types.d.ts +25 -0
- package/dist/transport/types.d.ts.map +1 -0
- package/dist/transport/types.js +11 -0
- package/dist/transport/types.js.map +1 -0
- package/dist/ui/index.d.ts +98 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +32 -0
- package/dist/ui/index.js.map +1 -0
- package/package.json +84 -0
- package/src/background/index.ts +152 -0
- package/src/background/register.ts +85 -0
- package/src/dev-reload.ts +60 -0
- package/src/envelope.ts +54 -0
- package/src/globals.ts +62 -0
- package/src/index.ts +171 -0
- package/src/modules/actions.ts +156 -0
- package/src/modules/auth.ts +68 -0
- package/src/modules/base64.ts +87 -0
- package/src/modules/blob.ts +341 -0
- package/src/modules/camera.ts +242 -0
- package/src/modules/cloud.ts +94 -0
- package/src/modules/dashboard.ts +31 -0
- package/src/modules/display.ts +122 -0
- package/src/modules/events.ts +291 -0
- package/src/modules/glasses.ts +44 -0
- package/src/modules/heading.ts +23 -0
- package/src/modules/imu.ts +44 -0
- package/src/modules/input.ts +71 -0
- package/src/modules/led.ts +61 -0
- package/src/modules/location.ts +39 -0
- package/src/modules/mic.ts +74 -0
- package/src/modules/miniapps.ts +90 -0
- package/src/modules/navigation.ts +670 -0
- package/src/modules/permissions.ts +84 -0
- package/src/modules/phone.ts +155 -0
- package/src/modules/pivots/engine.ts +863 -0
- package/src/modules/pivots/geometry.ts +409 -0
- package/src/modules/pivots/instructions.ts +343 -0
- package/src/modules/speaker.ts +335 -0
- package/src/modules/storage.ts +109 -0
- package/src/modules/stream.ts +163 -0
- package/src/modules/system.ts +76 -0
- package/src/modules/transcription.ts +145 -0
- package/src/modules/translation.ts +149 -0
- package/src/modules/ui.ts +498 -0
- package/src/protocol.ts +378 -0
- package/src/react/MentraProvider.tsx +50 -0
- package/src/react/MiniappHeader.tsx +122 -0
- package/src/react/index.ts +12 -0
- package/src/react/useCapabilities.ts +29 -0
- package/src/react/useCapsuleHeaderStyle.ts +54 -0
- package/src/react/useColorScheme.ts +26 -0
- package/src/react/useConnected.ts +47 -0
- package/src/react/useRpc.ts +106 -0
- package/src/react/useSafeArea.ts +40 -0
- package/src/session.ts +886 -0
- package/src/sub-path-types.test-d.ts +49 -0
- package/src/transport/auto.ts +143 -0
- package/src/transport/dispatch.ts +106 -0
- package/src/transport/local-socket.ts +93 -0
- package/src/transport/mock.ts +251 -0
- package/src/transport/postmessage.ts +95 -0
- package/src/transport/types.ts +26 -0
- package/src/ui/index.ts +122 -0
|
@@ -0,0 +1,670 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NavigationModule — turn-by-turn navigation for miniapps. A thin pass-through over the bridge;
|
|
3
|
+
* the phone-side daemon owns the trip lifecycle.
|
|
4
|
+
*
|
|
5
|
+
* Works on iOS and Android (both back it with the Mapbox Navigation SDK). Every Promise
|
|
6
|
+
* resolves with an `{ok, error?}` shape; nothing throws, so check `result.ok`.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {MiniappRequestType, MiniappStreamType} from "../protocol"
|
|
10
|
+
import {MiniappSession} from "../session"
|
|
11
|
+
import type {LocationData, UnsubscribeFn} from "./events"
|
|
12
|
+
import {PivotEngine} from "./pivots/engine"
|
|
13
|
+
|
|
14
|
+
export type LatLng = {lat: number; lng: number}
|
|
15
|
+
|
|
16
|
+
export type TravelMode = "walking" | "driving" | "cycling" | "two_wheeler"
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Categorical maneuver vocabulary. Most values come from the Google Nav SDK + Routes API;
|
|
20
|
+
* "CROSS_STREET" is SDK-synthesized when the pivot engine detects a crosswalk leg.
|
|
21
|
+
*/
|
|
22
|
+
export type ManeuverKind =
|
|
23
|
+
| "STRAIGHT"
|
|
24
|
+
| "CONTINUE"
|
|
25
|
+
| "SLIGHT_LEFT"
|
|
26
|
+
| "SLIGHT_RIGHT"
|
|
27
|
+
| "TURN_LEFT"
|
|
28
|
+
| "TURN_RIGHT"
|
|
29
|
+
| "SHARP_LEFT"
|
|
30
|
+
| "SHARP_RIGHT"
|
|
31
|
+
| "U_TURN"
|
|
32
|
+
| "NAME_CHANGE"
|
|
33
|
+
| "DEPART"
|
|
34
|
+
| "ARRIVE"
|
|
35
|
+
| "CROSS_STREET"
|
|
36
|
+
|
|
37
|
+
/** Routing preferences. All flags default to false. */
|
|
38
|
+
export type RouteAvoidances = {
|
|
39
|
+
highways?: boolean
|
|
40
|
+
tolls?: boolean
|
|
41
|
+
ferries?: boolean
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type NavManeuver = {
|
|
45
|
+
kind: "maneuver"
|
|
46
|
+
maneuverType: ManeuverKind
|
|
47
|
+
/** Meters to the maneuver. -1 if unknown. */
|
|
48
|
+
distanceMeters: number
|
|
49
|
+
/** Road the user is currently on. Null when unnamed or pre-first-update. */
|
|
50
|
+
fromRoad?: string | null
|
|
51
|
+
/** Legacy "next road" field. Prefer `nextStepRoad` for new UIs. */
|
|
52
|
+
toRoad?: string | null
|
|
53
|
+
/** Road the user will be on AFTER the maneuver. Use as the "next street" label. */
|
|
54
|
+
nextStepRoad?: string | null
|
|
55
|
+
|
|
56
|
+
/** Total meters to the destination. -1 if unknown. */
|
|
57
|
+
distanceToDestinationMeters?: number
|
|
58
|
+
/** Remaining travel time in seconds. -1 if unknown. */
|
|
59
|
+
timeToDestinationSeconds?: number
|
|
60
|
+
|
|
61
|
+
/** Current speed in m/s. Null if unavailable. */
|
|
62
|
+
currentSpeedMps?: number | null
|
|
63
|
+
/** Speed limit in m/s. Null if unknown / not regulated. */
|
|
64
|
+
speedLimitMps?: number | null
|
|
65
|
+
/** Route bearing at the user's position, 0–360. Null if unknown. */
|
|
66
|
+
routeHeadingDeg?: number | null
|
|
67
|
+
/** Host engine's verbatim instruction (e.g. "Turn left onto Waller St"). Render as-is. */
|
|
68
|
+
instruction?: string | null
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export type NavOffRoute = {
|
|
72
|
+
kind: "off_route"
|
|
73
|
+
/** Approximate perpendicular distance from the route, in meters. */
|
|
74
|
+
offRouteDistanceMeters: number
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export type NavRerouting = {kind: "rerouting"}
|
|
78
|
+
export type NavArrived = {kind: "arrived"}
|
|
79
|
+
export type NavError = {kind: "error"; message: string}
|
|
80
|
+
|
|
81
|
+
export type NavUpdate = NavManeuver | NavOffRoute | NavRerouting | NavArrived | NavError
|
|
82
|
+
|
|
83
|
+
export type NavRoute = {
|
|
84
|
+
points: LatLng[]
|
|
85
|
+
totalDistanceMeters?: number
|
|
86
|
+
totalDurationSeconds?: number
|
|
87
|
+
/** Ordered navigable segments. Omitted by older hosts that don't supply step metadata. */
|
|
88
|
+
steps?: NavStep[]
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Pivot-detection tuning. Omitted fields use mode-aware defaults. */
|
|
92
|
+
export type PivotOptions = {
|
|
93
|
+
/** "Turning now" radius. Defaults: walking 7, cycling 15, driving 40. */
|
|
94
|
+
radiusMeters?: number
|
|
95
|
+
/** "Approaching" radius. Defaults: walking 100, cycling 300, driving 800. */
|
|
96
|
+
approachThresholdMeters?: number
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** A real turn along the route, derived once per route build (re-derived on reroute). */
|
|
100
|
+
export type Pivot = {
|
|
101
|
+
/** 0-based ordinal; invalidated when `onRoute` fires again. */
|
|
102
|
+
index: number
|
|
103
|
+
lat: number
|
|
104
|
+
lng: number
|
|
105
|
+
direction: "left" | "right"
|
|
106
|
+
/** Road approaching the pivot. Null when the engine has no name. */
|
|
107
|
+
fromRoad: string | null
|
|
108
|
+
/** Road exiting the pivot. Same null semantics. */
|
|
109
|
+
toRoad: string | null
|
|
110
|
+
maneuver: ManeuverKind
|
|
111
|
+
/** Meters from trip start to this pivot, along the route. */
|
|
112
|
+
distanceAlongRouteMeters: number
|
|
113
|
+
/** "Turning now" radius for this pivot, in meters. */
|
|
114
|
+
radiusMeters: number
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Each pivot fires `approaching` → `entered` → `exited` (once each), then the cursor advances. */
|
|
118
|
+
export type PivotEvent =
|
|
119
|
+
| {kind: "approaching"; pivot: Pivot; distanceMeters: number}
|
|
120
|
+
| {kind: "entered"; pivot: Pivot}
|
|
121
|
+
| {kind: "exited"; pivot: Pivot}
|
|
122
|
+
|
|
123
|
+
/** One route leg: a stretch of road ending in a maneuver. */
|
|
124
|
+
export type NavStep = {
|
|
125
|
+
/** Coordinate where this step begins (== end of the previous step). */
|
|
126
|
+
lat: number
|
|
127
|
+
lng: number
|
|
128
|
+
/** Index into `NavRoute.points[]` where this step starts. */
|
|
129
|
+
routeIndex: number
|
|
130
|
+
/** Road traversed during this step. Null when unnamed. */
|
|
131
|
+
road?: string | null
|
|
132
|
+
/** Maneuver performed at the END of this step. */
|
|
133
|
+
maneuver: ManeuverKind
|
|
134
|
+
distanceMeters: number
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** One type-ahead place suggestion from `placeAutocomplete`. */
|
|
138
|
+
export type NavPlaceSuggestion = {
|
|
139
|
+
/** Opaque id to feed into `placeDetails`. */
|
|
140
|
+
placeId: string
|
|
141
|
+
/** Primary line, e.g. "Blue Bottle Coffee". */
|
|
142
|
+
mainText: string
|
|
143
|
+
/** Secondary line, e.g. "66 Mint St, San Francisco". Empty when none. */
|
|
144
|
+
secondaryText: string
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** A place resolved from a suggestion via `placeDetails` — has coordinates to route to. */
|
|
148
|
+
export type NavPlaceDetails = {
|
|
149
|
+
placeId: string
|
|
150
|
+
name: string
|
|
151
|
+
address: string
|
|
152
|
+
lat: number
|
|
153
|
+
lng: number
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export type NavigationDev = {
|
|
157
|
+
deviate(offsetMeters?: number): void
|
|
158
|
+
setWrongSidewalkOffset(enabled: boolean): void
|
|
159
|
+
setSkipCrossings(enabled: boolean): void
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export type StartNavigationOptions = {
|
|
163
|
+
/** Single-destination shorthand. Rewritten to `stops: [{lat, lng}]`. */
|
|
164
|
+
lat?: number
|
|
165
|
+
lng?: number
|
|
166
|
+
/** Ordered stops; last is the final destination. Must have ≥1 entry. */
|
|
167
|
+
stops?: LatLng[]
|
|
168
|
+
/** Defaults to "driving". */
|
|
169
|
+
mode?: TravelMode
|
|
170
|
+
avoid?: RouteAvoidances
|
|
171
|
+
/** Dev/testing only — fake walking the route at speedMultiplier×. */
|
|
172
|
+
simulate?: boolean
|
|
173
|
+
speedMultiplier?: number
|
|
174
|
+
/** Override the default pivot-detection knobs for this trip. */
|
|
175
|
+
pivots?: PivotOptions
|
|
176
|
+
/** Reroute once the user is this many meters past a missed pivot. Omit / 0 to use host defaults. */
|
|
177
|
+
missedTurnRerouteMeters?: number
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Snapshot of the active trip, shaped like the streaming events so mid-trip opens render the same. */
|
|
181
|
+
export type NavState = {
|
|
182
|
+
active: boolean
|
|
183
|
+
mode?: TravelMode
|
|
184
|
+
stops?: LatLng[]
|
|
185
|
+
/** Index of the stop currently being navigated to (0 = first). */
|
|
186
|
+
currentStopIndex?: number
|
|
187
|
+
route?: NavRoute
|
|
188
|
+
maneuver?: NavManeuver
|
|
189
|
+
distanceToDestinationMeters?: number
|
|
190
|
+
timeToDestinationSeconds?: number
|
|
191
|
+
currentSpeedMps?: number | null
|
|
192
|
+
speedLimitMps?: number | null
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export type NavPermissionResult = {
|
|
196
|
+
/** True if the request reached the host. False e.g. when LOCATION isn't declared in the manifest. */
|
|
197
|
+
ok: boolean
|
|
198
|
+
/** True if the user accepted the Nav SDK T&Cs (always true on platforms without a T&C gate). */
|
|
199
|
+
accepted: boolean
|
|
200
|
+
error?: string
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** Standalone route compute — does NOT start a trip. */
|
|
204
|
+
export type ComputeRouteOptions = {
|
|
205
|
+
origin: LatLng
|
|
206
|
+
/** ≥1 entry; last is the final destination. */
|
|
207
|
+
stops: LatLng[]
|
|
208
|
+
/** Defaults to "driving". */
|
|
209
|
+
mode?: TravelMode
|
|
210
|
+
avoid?: RouteAvoidances
|
|
211
|
+
/** Up to N alternate routes (engine-permitting). Default 1. */
|
|
212
|
+
alternatives?: number
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** One step of a previewed route, available BEFORE `start()`. Maneuver ENDS the segment. */
|
|
216
|
+
export type ComputedRouteStep = {
|
|
217
|
+
lat: number
|
|
218
|
+
lng: number
|
|
219
|
+
endLat: number
|
|
220
|
+
endLng: number
|
|
221
|
+
distanceMeters: number
|
|
222
|
+
maneuver?: ManeuverKind
|
|
223
|
+
/** Full instruction from the Routes API (e.g. "Turn left onto Fell St"). */
|
|
224
|
+
instruction?: string
|
|
225
|
+
/** Resolved road name. Prefer this over parsing `instruction`. Null when none found. */
|
|
226
|
+
road?: string | null
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export type ComputedRoute = {
|
|
230
|
+
points: LatLng[]
|
|
231
|
+
totalDistanceMeters: number
|
|
232
|
+
totalDurationSeconds: number
|
|
233
|
+
summary?: string
|
|
234
|
+
steps?: ComputedRouteStep[]
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export type ComputeRouteResult = {
|
|
238
|
+
ok: boolean
|
|
239
|
+
error?: string
|
|
240
|
+
/** Primary route first, alternates after. */
|
|
241
|
+
routes?: ComputedRoute[]
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export class NavigationModule {
|
|
245
|
+
private _pivots: PivotEngine = new PivotEngine("walking", undefined)
|
|
246
|
+
/** Subscriptions owned while a trip is active. Released on stop(). */
|
|
247
|
+
private _tripUnsubs: Array<() => void> = []
|
|
248
|
+
/** Trip params captured at start() so reroutes can re-issue computeRoute. */
|
|
249
|
+
private _tripStops: LatLng[] | null = null
|
|
250
|
+
private _tripMode: TravelMode = "walking"
|
|
251
|
+
private _tripAvoid: RouteAvoidances | undefined = undefined
|
|
252
|
+
/** Bumps per request so a stale reply from before a newer reroute is ignored. */
|
|
253
|
+
private _computeRouteSeq = 0
|
|
254
|
+
/** Latest GPS position, used as the origin for reroute computeRoute calls. */
|
|
255
|
+
private _lastUserPosition: LatLng | null = null
|
|
256
|
+
|
|
257
|
+
constructor(private readonly session: MiniappSession) {
|
|
258
|
+
// Reverse-geocode fallback for pivots whose instruction carried no road name.
|
|
259
|
+
this._pivots.setRoadNameResolver(async (coord) => {
|
|
260
|
+
try {
|
|
261
|
+
const result = await this.reverseGeocodeRoad(coord)
|
|
262
|
+
if (!result.ok) return null
|
|
263
|
+
return result.road ?? null
|
|
264
|
+
} catch {
|
|
265
|
+
return null
|
|
266
|
+
}
|
|
267
|
+
})
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** True iff `LOCATION` is declared in the miniapp's manifest. */
|
|
271
|
+
get hasPermission(): boolean {
|
|
272
|
+
return this.session._hasManifestPermission("LOCATION")
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Trigger the Nav SDK Terms & Conditions dialog up front, before the user hits "start".
|
|
277
|
+
* Idempotent. Resolves `{ok: false, accepted: false, error}` when LOCATION isn't declared.
|
|
278
|
+
*/
|
|
279
|
+
requestPermission(): Promise<NavPermissionResult> {
|
|
280
|
+
if (!this.hasPermission) {
|
|
281
|
+
return Promise.resolve({
|
|
282
|
+
ok: false,
|
|
283
|
+
accepted: false,
|
|
284
|
+
error: "LOCATION permission not declared in miniapp.json (required for navigation.requestPermission).",
|
|
285
|
+
})
|
|
286
|
+
}
|
|
287
|
+
return this.session.sendRequest<NavPermissionResult>({
|
|
288
|
+
type: MiniappRequestType.NAVIGATION_REQUEST_PERMISSION,
|
|
289
|
+
})
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Start a navigation session. Pass `{lat, lng}` or `{stops: [...]}`. `{ok: true}` means the
|
|
294
|
+
* daemon accepted the request, not that a route was built — listen via `onUpdate(...)`.
|
|
295
|
+
*/
|
|
296
|
+
async start(opts: StartNavigationOptions): Promise<{ok: boolean; error?: string}> {
|
|
297
|
+
if (!this.hasPermission) {
|
|
298
|
+
return {
|
|
299
|
+
ok: false,
|
|
300
|
+
error: "LOCATION permission not declared in miniapp.json (required for navigation.start).",
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
const stops = normalizeStops(opts)
|
|
304
|
+
const mode = opts.mode ?? "driving"
|
|
305
|
+
|
|
306
|
+
this._tripStops = stops.length > 0 ? stops : null
|
|
307
|
+
this._tripMode = mode
|
|
308
|
+
this._tripAvoid = opts.avoid
|
|
309
|
+
|
|
310
|
+
// Subscribes the engine to route + location streams. Idempotent — detaches any prior trip first.
|
|
311
|
+
this._attachPivotTrackingForTrip(mode, opts.pivots)
|
|
312
|
+
|
|
313
|
+
// Every failure return below must release the subscriptions attached just above.
|
|
314
|
+
const failStart = (error: string): {ok: false; error: string} => {
|
|
315
|
+
this._detachPivotTracking()
|
|
316
|
+
return {ok: false, error}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (stops.length === 0) {
|
|
320
|
+
return failStart("navigation.start: no stops")
|
|
321
|
+
}
|
|
322
|
+
// Origin: live position if known, else a one-shot host fetch. (stops[0] would be the
|
|
323
|
+
// destination for a single-stop trip — a degenerate origin.)
|
|
324
|
+
let origin: LatLng | null = this._lastUserPosition
|
|
325
|
+
if (!origin) {
|
|
326
|
+
try {
|
|
327
|
+
const fix = await this.session.location.getOnce()
|
|
328
|
+
origin = {lat: fix.lat, lng: fix.lng}
|
|
329
|
+
this._lastUserPosition = origin
|
|
330
|
+
} catch (err) {
|
|
331
|
+
return failStart(`navigation.start: no GPS fix (${err instanceof Error ? err.message : String(err)})`)
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Source-of-truth route is the host's Routes REST call. If it fails, abort (no native fallback).
|
|
336
|
+
const restResult = await this.computeRoute({origin, stops, mode, avoid: opts.avoid})
|
|
337
|
+
if (!restResult.ok || !restResult.routes || restResult.routes.length === 0) {
|
|
338
|
+
return failStart(restResult.error ?? "computeRoute failed")
|
|
339
|
+
}
|
|
340
|
+
const restRoute = restResult.routes[0]
|
|
341
|
+
|
|
342
|
+
const nativeResult = await this.session.sendRequest<{ok: boolean; error?: string}>({
|
|
343
|
+
type: MiniappRequestType.NAVIGATION_START,
|
|
344
|
+
// Keep lat/lng on the wire for hosts not yet upgraded to read `stops`.
|
|
345
|
+
lat: stops[0]?.lat,
|
|
346
|
+
lng: stops[0]?.lng,
|
|
347
|
+
stops,
|
|
348
|
+
mode,
|
|
349
|
+
avoid: opts.avoid,
|
|
350
|
+
simulate: opts.simulate ?? false,
|
|
351
|
+
speedMultiplier: opts.speedMultiplier ?? 1,
|
|
352
|
+
missedTurnRerouteMeters: opts.missedTurnRerouteMeters,
|
|
353
|
+
})
|
|
354
|
+
if (!nativeResult.ok) {
|
|
355
|
+
this._detachPivotTracking()
|
|
356
|
+
return nativeResult
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Dispatch the REST route through the native route channel so subscribers see step roads now.
|
|
360
|
+
const syntheticRoute = buildNavRouteFromComputed(restRoute)
|
|
361
|
+
this.session.events._forwardEvent(MiniappStreamType.NAVIGATION_ROUTE, syntheticRoute)
|
|
362
|
+
return nativeResult
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** Stop the active session (no-op if none). Fire-and-forget; also detaches pivot tracking. */
|
|
366
|
+
stop(): void {
|
|
367
|
+
this._detachPivotTracking()
|
|
368
|
+
this.session.sendOneShot({type: MiniappRequestType.NAVIGATION_STOP})
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/** Dev-only simulator helpers for testing trips. `deviate` works on both platforms;
|
|
372
|
+
* a few helpers are Android-only and return `{ok: false}` on iOS. */
|
|
373
|
+
get dev(): NavigationDev {
|
|
374
|
+
return {
|
|
375
|
+
/** Nudge the simulator off-route to trigger a reroute. Default 20m. */
|
|
376
|
+
deviate: (offsetMeters: number = 20): void => {
|
|
377
|
+
this.session.sendOneShot({type: MiniappRequestType.NAVIGATION_DEVIATE, offsetMeters})
|
|
378
|
+
},
|
|
379
|
+
/** Lock simulated locations to the wrong sidewalk to test along-path pivot triggers. */
|
|
380
|
+
setWrongSidewalkOffset: (enabled: boolean): void => {
|
|
381
|
+
this.session.sendOneShot({type: MiniappRequestType.NAVIGATION_SET_WRONG_SIDEWALK, enabled})
|
|
382
|
+
},
|
|
383
|
+
/** Walk a polyline with crossing micro-steps removed, for missed-turn testing. */
|
|
384
|
+
setSkipCrossings: (enabled: boolean): void => {
|
|
385
|
+
this.session.sendOneShot({type: MiniappRequestType.NAVIGATION_SET_SKIP_CROSSINGS, enabled})
|
|
386
|
+
},
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/** Subscribe to live trip events (maneuver / off_route / rerouting / arrived / error). */
|
|
391
|
+
onUpdate(handler: (update: NavUpdate) => void): UnsubscribeFn {
|
|
392
|
+
return this.session._subscribe(MiniappStreamType.NAVIGATION_UPDATE, handler as (data: unknown) => void)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/** Subscribe to the route polyline. Fires once per route build (initial + reroute), full path. */
|
|
396
|
+
onRoute(handler: (route: NavRoute) => void): UnsubscribeFn {
|
|
397
|
+
return this.session._subscribe(MiniappStreamType.NAVIGATION_ROUTE, handler as (data: unknown) => void)
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/** Snapshot of the active trip, or `null` when none is running. Use on mount to hydrate state. */
|
|
401
|
+
async getState(): Promise<NavState | null> {
|
|
402
|
+
const result = await this.session.sendRequest<{ok: boolean; state?: NavState | null}>({
|
|
403
|
+
type: MiniappRequestType.NAVIGATION_GET_STATE,
|
|
404
|
+
})
|
|
405
|
+
return result.ok ? (result.state ?? null) : null
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/** Compute a route without starting a trip. Primary route first, alternates after. */
|
|
409
|
+
computeRoute(opts: ComputeRouteOptions): Promise<ComputeRouteResult> {
|
|
410
|
+
if (!this.hasPermission) {
|
|
411
|
+
return Promise.resolve({
|
|
412
|
+
ok: false,
|
|
413
|
+
error: "LOCATION permission not declared in miniapp.json (required for navigation.computeRoute).",
|
|
414
|
+
})
|
|
415
|
+
}
|
|
416
|
+
return this.session.sendRequest<ComputeRouteResult>({
|
|
417
|
+
type: MiniappRequestType.NAVIGATION_COMPUTE_ROUTE,
|
|
418
|
+
origin: opts.origin,
|
|
419
|
+
stops: opts.stops,
|
|
420
|
+
mode: opts.mode ?? "driving",
|
|
421
|
+
avoid: opts.avoid,
|
|
422
|
+
alternatives: opts.alternatives ?? 1,
|
|
423
|
+
})
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Reverse-geocode a coordinate into a short road name (`road`) and a full
|
|
428
|
+
* formatted street address (`address`, e.g. "369 Hayes Street, San Francisco,
|
|
429
|
+
* California 94102"). `road` backs the pivot engine's fallback; `address`
|
|
430
|
+
* backs UI labels for dropped pins / POI taps. Each is null when none of that
|
|
431
|
+
* kind is found near the coordinate; `ok: false` is an actual failure.
|
|
432
|
+
*
|
|
433
|
+
* Resolved entirely in the v2 cloud maps service (provider-abstracted, Mapbox
|
|
434
|
+
* today) via the host bridge — the miniapp never holds a maps token or calls a
|
|
435
|
+
* provider directly.
|
|
436
|
+
*/
|
|
437
|
+
reverseGeocodeRoad(
|
|
438
|
+
coord: LatLng,
|
|
439
|
+
): Promise<{ok: boolean; road?: string | null; address?: string | null; error?: string}> {
|
|
440
|
+
if (!this.hasPermission) {
|
|
441
|
+
return Promise.resolve({
|
|
442
|
+
ok: false,
|
|
443
|
+
error: "LOCATION permission not declared in miniapp.json (required for reverseGeocodeRoad).",
|
|
444
|
+
})
|
|
445
|
+
}
|
|
446
|
+
return this.session.sendRequest<{
|
|
447
|
+
ok: boolean
|
|
448
|
+
road?: string | null
|
|
449
|
+
address?: string | null
|
|
450
|
+
error?: string
|
|
451
|
+
}>({
|
|
452
|
+
type: MiniappRequestType.NAVIGATION_REVERSE_GEOCODE,
|
|
453
|
+
lat: coord.lat,
|
|
454
|
+
lng: coord.lng,
|
|
455
|
+
})
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Type-ahead place search. Returns lightweight suggestions for `query`,
|
|
460
|
+
* optionally biased toward `near`. Resolved in the v2 cloud maps service
|
|
461
|
+
* (Mapbox Search Box today) via the host bridge — no maps token in the
|
|
462
|
+
* miniapp. `sessionToken` should be a stable per-search-box-opening string,
|
|
463
|
+
* reused on the following `placeDetails` call, then rotated after a pick, so
|
|
464
|
+
* the keystrokes + the final retrieve bill as one search session.
|
|
465
|
+
*
|
|
466
|
+
* Does NOT require LOCATION permission — it's a text search; `near` is only a
|
|
467
|
+
* ranking bias. Resolves `{ok, suggestions}`; `ok: false` is an actual failure.
|
|
468
|
+
*/
|
|
469
|
+
placeAutocomplete(opts: {
|
|
470
|
+
query: string
|
|
471
|
+
near?: LatLng
|
|
472
|
+
sessionToken: string
|
|
473
|
+
}): Promise<{ok: boolean; suggestions?: NavPlaceSuggestion[]; error?: string}> {
|
|
474
|
+
return this.session.sendRequest<{
|
|
475
|
+
ok: boolean
|
|
476
|
+
suggestions?: NavPlaceSuggestion[]
|
|
477
|
+
error?: string
|
|
478
|
+
}>({
|
|
479
|
+
type: MiniappRequestType.NAVIGATION_PLACE_AUTOCOMPLETE,
|
|
480
|
+
query: opts.query,
|
|
481
|
+
near: opts.near ?? null,
|
|
482
|
+
sessionToken: opts.sessionToken,
|
|
483
|
+
})
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Resolve a suggestion's `placeId` (from `placeAutocomplete`) to a full place
|
|
488
|
+
* with coordinates. Pass the SAME `sessionToken` used for the autocomplete so
|
|
489
|
+
* the provider closes the billing session. Resolved in the v2 cloud maps
|
|
490
|
+
* service via the host bridge. Resolves `{ok, place}`; `ok: false` is a
|
|
491
|
+
* failure (including an unresolvable id).
|
|
492
|
+
*/
|
|
493
|
+
placeDetails(opts: {
|
|
494
|
+
placeId: string
|
|
495
|
+
sessionToken: string
|
|
496
|
+
}): Promise<{ok: boolean; place?: NavPlaceDetails; error?: string}> {
|
|
497
|
+
return this.session.sendRequest<{ok: boolean; place?: NavPlaceDetails; error?: string}>({
|
|
498
|
+
type: MiniappRequestType.NAVIGATION_PLACE_DETAILS,
|
|
499
|
+
placeId: opts.placeId,
|
|
500
|
+
sessionToken: opts.sessionToken,
|
|
501
|
+
})
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Pivots — real turns along the route, fired as approaching / entered / exited as GPS ticks in.
|
|
505
|
+
|
|
506
|
+
/** Subscribe to pivot events. Each pivot fires once per kind; passed pivots aren't re-evaluated. */
|
|
507
|
+
onPivot(handler: (event: PivotEvent) => void): UnsubscribeFn {
|
|
508
|
+
return this._pivots.subscribe(handler)
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/** Full pivot list for the active route. Empty before the first `onRoute`. */
|
|
512
|
+
getPivots(): Pivot[] {
|
|
513
|
+
return this._pivots.getPivots()
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/** The pivot the user is currently inside (between `entered` and `exited`), or `null`. */
|
|
517
|
+
getActivePivot(): Pivot | null {
|
|
518
|
+
return this._pivots.getActivePivot()
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/** The next pivot ahead, or `null` once every pivot has been passed. */
|
|
522
|
+
getUpcomingPivot(): Pivot | null {
|
|
523
|
+
return this._pivots.getUpcomingPivot()
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
private _attachPivotTrackingForTrip(mode: TravelMode, opts: PivotOptions | undefined): void {
|
|
527
|
+
this._detachPivotTracking()
|
|
528
|
+
this._pivots.reset()
|
|
529
|
+
this._pivots.updateOptions(mode, opts)
|
|
530
|
+
|
|
531
|
+
// Rebuild pivots on every route. Initial start emits a synthetic onRoute with REST steps (build
|
|
532
|
+
// directly); a native reroute emits no steps (render geometry pivots, then enrich via REST).
|
|
533
|
+
this._tripUnsubs.push(
|
|
534
|
+
this.onRoute((route) => {
|
|
535
|
+
if (route.steps && route.steps.length > 0) {
|
|
536
|
+
// NavStep carries only the step start; the engine needs endLat/endLng to anchor the turn.
|
|
537
|
+
// end of step[i] == start of step[i+1]; the final step uses the last polyline vertex.
|
|
538
|
+
const tail = route.points[route.points.length - 1]
|
|
539
|
+
const computedSteps = route.steps.map((s, i) => {
|
|
540
|
+
const next = route.steps?.[i + 1]
|
|
541
|
+
const endLat = next ? next.lat : (tail?.lat ?? s.lat)
|
|
542
|
+
const endLng = next ? next.lng : (tail?.lng ?? s.lng)
|
|
543
|
+
return {
|
|
544
|
+
lat: s.lat,
|
|
545
|
+
lng: s.lng,
|
|
546
|
+
endLat,
|
|
547
|
+
endLng,
|
|
548
|
+
distanceMeters: s.distanceMeters,
|
|
549
|
+
maneuver: s.maneuver,
|
|
550
|
+
road: s.road ?? undefined,
|
|
551
|
+
}
|
|
552
|
+
})
|
|
553
|
+
this._pivots.setRouteFromComputedSteps(route, computedSteps)
|
|
554
|
+
return
|
|
555
|
+
}
|
|
556
|
+
this._pivots.setRoute(route, null)
|
|
557
|
+
const seq = ++this._computeRouteSeq
|
|
558
|
+
this._issueComputeRouteForTrip(route)
|
|
559
|
+
.then((result) => {
|
|
560
|
+
if (seq !== this._computeRouteSeq) return // superseded by a newer reroute
|
|
561
|
+
const computedSteps = result?.routes?.[0]?.steps
|
|
562
|
+
if (computedSteps && computedSteps.length > 0) {
|
|
563
|
+
this._pivots.setRouteFromComputedSteps(route, computedSteps)
|
|
564
|
+
}
|
|
565
|
+
})
|
|
566
|
+
.catch((err) => {
|
|
567
|
+
console.warn("[NavigationModule] computeRoute for pivots failed:", err)
|
|
568
|
+
})
|
|
569
|
+
}),
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
// `arrived` resets the engine. `rerouting` is handled natively by the host, so no action here.
|
|
573
|
+
this._tripUnsubs.push(
|
|
574
|
+
this.onUpdate((update) => {
|
|
575
|
+
if (update.kind === "arrived") {
|
|
576
|
+
this._pivots.reset()
|
|
577
|
+
return
|
|
578
|
+
}
|
|
579
|
+
}),
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
// Every fix drives the pivot cursor and records the latest position for reroute origin.
|
|
583
|
+
this._tripUnsubs.push(
|
|
584
|
+
this.session.location.onUpdate((loc: LocationData) => {
|
|
585
|
+
this._lastUserPosition = {lat: loc.lat, lng: loc.lng}
|
|
586
|
+
this._pivots.onLocationUpdate({lat: loc.lat, lng: loc.lng})
|
|
587
|
+
}),
|
|
588
|
+
)
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/** computeRoute for the current trip, using the latest position (or route start) as origin. */
|
|
592
|
+
private async _issueComputeRouteForTrip(route?: NavRoute): Promise<ComputeRouteResult | null> {
|
|
593
|
+
if (!this._tripStops || this._tripStops.length === 0) return null
|
|
594
|
+
const origin: LatLng | null =
|
|
595
|
+
this._lastUserPosition ?? (route?.points && route.points[0] ? {lat: route.points[0].lat, lng: route.points[0].lng} : null)
|
|
596
|
+
if (!origin) return null
|
|
597
|
+
return this.computeRoute({
|
|
598
|
+
origin,
|
|
599
|
+
stops: this._tripStops,
|
|
600
|
+
mode: this._tripMode,
|
|
601
|
+
avoid: this._tripAvoid,
|
|
602
|
+
})
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// NOTE: client-side reroute was removed in the Mapbox migration — the host reroutes natively and
|
|
606
|
+
// pushes the new route through `onRoute`. If a future host doesn't, re-add it in `onUpdate`.
|
|
607
|
+
|
|
608
|
+
private _detachPivotTracking(): void {
|
|
609
|
+
for (const unsub of this._tripUnsubs) {
|
|
610
|
+
try {
|
|
611
|
+
unsub()
|
|
612
|
+
} catch (err) {
|
|
613
|
+
console.warn("[NavigationModule] pivot-tracking unsubscribe threw:", err)
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
this._tripUnsubs = []
|
|
617
|
+
this._pivots.reset()
|
|
618
|
+
// Bumping the sequence is the real guard against a stale in-flight reply; the rest is tidiness.
|
|
619
|
+
this._tripStops = null
|
|
620
|
+
this._tripAvoid = undefined
|
|
621
|
+
this._computeRouteSeq++
|
|
622
|
+
this._lastUserPosition = null
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/** Normalize the `{lat, lng}` shorthand into a `stops` array. */
|
|
627
|
+
function normalizeStops(opts: StartNavigationOptions): LatLng[] {
|
|
628
|
+
if (opts.stops && opts.stops.length > 0) {
|
|
629
|
+
return opts.stops
|
|
630
|
+
}
|
|
631
|
+
if (typeof opts.lat === "number" && typeof opts.lng === "number") {
|
|
632
|
+
return [{lat: opts.lat, lng: opts.lng}]
|
|
633
|
+
}
|
|
634
|
+
return []
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Convert a REST `ComputedRoute` into the wire `NavRoute` shape. Anchors each step to a polyline
|
|
639
|
+
* vertex (`routeIndex`) by nearest-point search. Steps with no maneuver default to "STRAIGHT".
|
|
640
|
+
*/
|
|
641
|
+
function buildNavRouteFromComputed(computed: ComputedRoute): NavRoute {
|
|
642
|
+
const points = computed.points ?? []
|
|
643
|
+
const steps: NavStep[] = (computed.steps ?? []).map((s) => {
|
|
644
|
+
let routeIndex = 0
|
|
645
|
+
let best = Infinity
|
|
646
|
+
for (let i = 0; i < points.length; i++) {
|
|
647
|
+
const dLat = points[i].lat - s.lat
|
|
648
|
+
const dLng = points[i].lng - s.lng
|
|
649
|
+
const d = dLat * dLat + dLng * dLng
|
|
650
|
+
if (d < best) {
|
|
651
|
+
best = d
|
|
652
|
+
routeIndex = i
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
return {
|
|
656
|
+
lat: s.lat,
|
|
657
|
+
lng: s.lng,
|
|
658
|
+
routeIndex,
|
|
659
|
+
road: s.road ?? null,
|
|
660
|
+
maneuver: s.maneuver ?? "STRAIGHT",
|
|
661
|
+
distanceMeters: s.distanceMeters,
|
|
662
|
+
}
|
|
663
|
+
})
|
|
664
|
+
return {
|
|
665
|
+
points,
|
|
666
|
+
totalDistanceMeters: computed.totalDistanceMeters,
|
|
667
|
+
totalDurationSeconds: computed.totalDurationSeconds,
|
|
668
|
+
steps: steps.length > 0 ? steps : undefined,
|
|
669
|
+
}
|
|
670
|
+
}
|