@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.
Files changed (277) hide show
  1. package/README.md +259 -0
  2. package/dist/background/index.d.ts +60 -0
  3. package/dist/background/index.d.ts.map +1 -0
  4. package/dist/background/index.js +25 -0
  5. package/dist/background/index.js.map +1 -0
  6. package/dist/background/register.d.ts +38 -0
  7. package/dist/background/register.d.ts.map +1 -0
  8. package/dist/background/register.js +75 -0
  9. package/dist/background/register.js.map +1 -0
  10. package/dist/dev-reload.d.ts +21 -0
  11. package/dist/dev-reload.d.ts.map +1 -0
  12. package/dist/dev-reload.js +62 -0
  13. package/dist/dev-reload.js.map +1 -0
  14. package/dist/envelope.d.ts +23 -0
  15. package/dist/envelope.d.ts.map +1 -0
  16. package/dist/envelope.js +47 -0
  17. package/dist/envelope.js.map +1 -0
  18. package/dist/globals.d.ts +54 -0
  19. package/dist/globals.d.ts.map +1 -0
  20. package/dist/globals.js +12 -0
  21. package/dist/globals.js.map +1 -0
  22. package/dist/index.d.ts +59 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +31 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/modules/actions.d.ts +54 -0
  27. package/dist/modules/actions.d.ts.map +1 -0
  28. package/dist/modules/actions.js +122 -0
  29. package/dist/modules/actions.js.map +1 -0
  30. package/dist/modules/auth.d.ts +25 -0
  31. package/dist/modules/auth.d.ts.map +1 -0
  32. package/dist/modules/auth.js +52 -0
  33. package/dist/modules/auth.js.map +1 -0
  34. package/dist/modules/base64.d.ts +16 -0
  35. package/dist/modules/base64.d.ts.map +1 -0
  36. package/dist/modules/base64.js +92 -0
  37. package/dist/modules/base64.js.map +1 -0
  38. package/dist/modules/blob.d.ts +161 -0
  39. package/dist/modules/blob.d.ts.map +1 -0
  40. package/dist/modules/blob.js +277 -0
  41. package/dist/modules/blob.js.map +1 -0
  42. package/dist/modules/camera.d.ts +170 -0
  43. package/dist/modules/camera.d.ts.map +1 -0
  44. package/dist/modules/camera.js +116 -0
  45. package/dist/modules/camera.js.map +1 -0
  46. package/dist/modules/cloud.d.ts +34 -0
  47. package/dist/modules/cloud.d.ts.map +1 -0
  48. package/dist/modules/cloud.js +70 -0
  49. package/dist/modules/cloud.js.map +1 -0
  50. package/dist/modules/dashboard.d.ts +16 -0
  51. package/dist/modules/dashboard.d.ts.map +1 -0
  52. package/dist/modules/dashboard.js +27 -0
  53. package/dist/modules/dashboard.js.map +1 -0
  54. package/dist/modules/display.d.ts +106 -0
  55. package/dist/modules/display.d.ts.map +1 -0
  56. package/dist/modules/display.js +57 -0
  57. package/dist/modules/display.js.map +1 -0
  58. package/dist/modules/events.d.ts +160 -0
  59. package/dist/modules/events.d.ts.map +1 -0
  60. package/dist/modules/events.js +168 -0
  61. package/dist/modules/events.js.map +1 -0
  62. package/dist/modules/glasses.d.ts +28 -0
  63. package/dist/modules/glasses.d.ts.map +1 -0
  64. package/dist/modules/glasses.js +39 -0
  65. package/dist/modules/glasses.js.map +1 -0
  66. package/dist/modules/heading.d.ts +16 -0
  67. package/dist/modules/heading.d.ts.map +1 -0
  68. package/dist/modules/heading.js +20 -0
  69. package/dist/modules/heading.js.map +1 -0
  70. package/dist/modules/imu.d.ts +31 -0
  71. package/dist/modules/imu.d.ts.map +1 -0
  72. package/dist/modules/imu.js +40 -0
  73. package/dist/modules/imu.js.map +1 -0
  74. package/dist/modules/input.d.ts +34 -0
  75. package/dist/modules/input.d.ts.map +1 -0
  76. package/dist/modules/input.js +50 -0
  77. package/dist/modules/input.js.map +1 -0
  78. package/dist/modules/led.d.ts +35 -0
  79. package/dist/modules/led.d.ts.map +1 -0
  80. package/dist/modules/led.js +39 -0
  81. package/dist/modules/led.js.map +1 -0
  82. package/dist/modules/location.d.ts +27 -0
  83. package/dist/modules/location.d.ts.map +1 -0
  84. package/dist/modules/location.js +35 -0
  85. package/dist/modules/location.js.map +1 -0
  86. package/dist/modules/mic.d.ts +44 -0
  87. package/dist/modules/mic.d.ts.map +1 -0
  88. package/dist/modules/mic.js +67 -0
  89. package/dist/modules/mic.js.map +1 -0
  90. package/dist/modules/miniapps.d.ts +72 -0
  91. package/dist/modules/miniapps.d.ts.map +1 -0
  92. package/dist/modules/miniapps.js +48 -0
  93. package/dist/modules/miniapps.js.map +1 -0
  94. package/dist/modules/navigation.d.ts +326 -0
  95. package/dist/modules/navigation.d.ts.map +1 -0
  96. package/dist/modules/navigation.js +386 -0
  97. package/dist/modules/navigation.js.map +1 -0
  98. package/dist/modules/permissions.d.ts +58 -0
  99. package/dist/modules/permissions.d.ts.map +1 -0
  100. package/dist/modules/permissions.js +66 -0
  101. package/dist/modules/permissions.js.map +1 -0
  102. package/dist/modules/phone.d.ts +95 -0
  103. package/dist/modules/phone.d.ts.map +1 -0
  104. package/dist/modules/phone.js +110 -0
  105. package/dist/modules/phone.js.map +1 -0
  106. package/dist/modules/pivots/engine.d.ts +169 -0
  107. package/dist/modules/pivots/engine.d.ts.map +1 -0
  108. package/dist/modules/pivots/engine.js +779 -0
  109. package/dist/modules/pivots/engine.js.map +1 -0
  110. package/dist/modules/pivots/geometry.d.ts +84 -0
  111. package/dist/modules/pivots/geometry.d.ts.map +1 -0
  112. package/dist/modules/pivots/geometry.js +371 -0
  113. package/dist/modules/pivots/geometry.js.map +1 -0
  114. package/dist/modules/pivots/instructions.d.ts +139 -0
  115. package/dist/modules/pivots/instructions.d.ts.map +1 -0
  116. package/dist/modules/pivots/instructions.js +280 -0
  117. package/dist/modules/pivots/instructions.js.map +1 -0
  118. package/dist/modules/speaker.d.ts +169 -0
  119. package/dist/modules/speaker.d.ts.map +1 -0
  120. package/dist/modules/speaker.js +251 -0
  121. package/dist/modules/speaker.js.map +1 -0
  122. package/dist/modules/storage.d.ts +50 -0
  123. package/dist/modules/storage.d.ts.map +1 -0
  124. package/dist/modules/storage.js +99 -0
  125. package/dist/modules/storage.js.map +1 -0
  126. package/dist/modules/stream.d.ts +129 -0
  127. package/dist/modules/stream.d.ts.map +1 -0
  128. package/dist/modules/stream.js +52 -0
  129. package/dist/modules/stream.js.map +1 -0
  130. package/dist/modules/system.d.ts +45 -0
  131. package/dist/modules/system.d.ts.map +1 -0
  132. package/dist/modules/system.js +42 -0
  133. package/dist/modules/system.js.map +1 -0
  134. package/dist/modules/transcription.d.ts +75 -0
  135. package/dist/modules/transcription.d.ts.map +1 -0
  136. package/dist/modules/transcription.js +106 -0
  137. package/dist/modules/transcription.js.map +1 -0
  138. package/dist/modules/translation.d.ts +59 -0
  139. package/dist/modules/translation.d.ts.map +1 -0
  140. package/dist/modules/translation.js +119 -0
  141. package/dist/modules/translation.js.map +1 -0
  142. package/dist/modules/ui.d.ts +186 -0
  143. package/dist/modules/ui.d.ts.map +1 -0
  144. package/dist/modules/ui.js +293 -0
  145. package/dist/modules/ui.js.map +1 -0
  146. package/dist/protocol.d.ts +299 -0
  147. package/dist/protocol.d.ts.map +1 -0
  148. package/dist/protocol.js +325 -0
  149. package/dist/protocol.js.map +1 -0
  150. package/dist/react/MentraProvider.d.ts +33 -0
  151. package/dist/react/MentraProvider.d.ts.map +1 -0
  152. package/dist/react/MentraProvider.js +15 -0
  153. package/dist/react/MentraProvider.js.map +1 -0
  154. package/dist/react/MiniappHeader.d.ts +44 -0
  155. package/dist/react/MiniappHeader.d.ts.map +1 -0
  156. package/dist/react/MiniappHeader.js +32 -0
  157. package/dist/react/MiniappHeader.js.map +1 -0
  158. package/dist/react/index.d.ts +13 -0
  159. package/dist/react/index.d.ts.map +1 -0
  160. package/dist/react/index.js +9 -0
  161. package/dist/react/index.js.map +1 -0
  162. package/dist/react/useCapabilities.d.ts +26 -0
  163. package/dist/react/useCapabilities.d.ts.map +1 -0
  164. package/dist/react/useCapabilities.js +27 -0
  165. package/dist/react/useCapabilities.js.map +1 -0
  166. package/dist/react/useCapsuleHeaderStyle.d.ts +27 -0
  167. package/dist/react/useCapsuleHeaderStyle.d.ts.map +1 -0
  168. package/dist/react/useCapsuleHeaderStyle.js +36 -0
  169. package/dist/react/useCapsuleHeaderStyle.js.map +1 -0
  170. package/dist/react/useColorScheme.d.ts +18 -0
  171. package/dist/react/useColorScheme.d.ts.map +1 -0
  172. package/dist/react/useColorScheme.js +24 -0
  173. package/dist/react/useColorScheme.js.map +1 -0
  174. package/dist/react/useConnected.d.ts +17 -0
  175. package/dist/react/useConnected.d.ts.map +1 -0
  176. package/dist/react/useConnected.js +39 -0
  177. package/dist/react/useConnected.js.map +1 -0
  178. package/dist/react/useRpc.d.ts +29 -0
  179. package/dist/react/useRpc.d.ts.map +1 -0
  180. package/dist/react/useRpc.js +82 -0
  181. package/dist/react/useRpc.js.map +1 -0
  182. package/dist/react/useSafeArea.d.ts +21 -0
  183. package/dist/react/useSafeArea.d.ts.map +1 -0
  184. package/dist/react/useSafeArea.js +22 -0
  185. package/dist/react/useSafeArea.js.map +1 -0
  186. package/dist/session.d.ts +312 -0
  187. package/dist/session.d.ts.map +1 -0
  188. package/dist/session.js +653 -0
  189. package/dist/session.js.map +1 -0
  190. package/dist/sub-path-types.test-d.d.ts +28 -0
  191. package/dist/sub-path-types.test-d.d.ts.map +1 -0
  192. package/dist/sub-path-types.test-d.js +37 -0
  193. package/dist/sub-path-types.test-d.js.map +1 -0
  194. package/dist/transport/auto.d.ts +35 -0
  195. package/dist/transport/auto.d.ts.map +1 -0
  196. package/dist/transport/auto.js +125 -0
  197. package/dist/transport/auto.js.map +1 -0
  198. package/dist/transport/dispatch.d.ts +43 -0
  199. package/dist/transport/dispatch.d.ts.map +1 -0
  200. package/dist/transport/dispatch.js +91 -0
  201. package/dist/transport/dispatch.js.map +1 -0
  202. package/dist/transport/local-socket.d.ts +28 -0
  203. package/dist/transport/local-socket.d.ts.map +1 -0
  204. package/dist/transport/local-socket.js +80 -0
  205. package/dist/transport/local-socket.js.map +1 -0
  206. package/dist/transport/mock.d.ts +54 -0
  207. package/dist/transport/mock.d.ts.map +1 -0
  208. package/dist/transport/mock.js +207 -0
  209. package/dist/transport/mock.js.map +1 -0
  210. package/dist/transport/postmessage.d.ts +36 -0
  211. package/dist/transport/postmessage.d.ts.map +1 -0
  212. package/dist/transport/postmessage.js +78 -0
  213. package/dist/transport/postmessage.js.map +1 -0
  214. package/dist/transport/types.d.ts +25 -0
  215. package/dist/transport/types.d.ts.map +1 -0
  216. package/dist/transport/types.js +11 -0
  217. package/dist/transport/types.js.map +1 -0
  218. package/dist/ui/index.d.ts +98 -0
  219. package/dist/ui/index.d.ts.map +1 -0
  220. package/dist/ui/index.js +32 -0
  221. package/dist/ui/index.js.map +1 -0
  222. package/package.json +84 -0
  223. package/src/background/index.ts +152 -0
  224. package/src/background/register.ts +85 -0
  225. package/src/dev-reload.ts +60 -0
  226. package/src/envelope.ts +54 -0
  227. package/src/globals.ts +62 -0
  228. package/src/index.ts +171 -0
  229. package/src/modules/actions.ts +156 -0
  230. package/src/modules/auth.ts +68 -0
  231. package/src/modules/base64.ts +87 -0
  232. package/src/modules/blob.ts +341 -0
  233. package/src/modules/camera.ts +242 -0
  234. package/src/modules/cloud.ts +94 -0
  235. package/src/modules/dashboard.ts +31 -0
  236. package/src/modules/display.ts +122 -0
  237. package/src/modules/events.ts +291 -0
  238. package/src/modules/glasses.ts +44 -0
  239. package/src/modules/heading.ts +23 -0
  240. package/src/modules/imu.ts +44 -0
  241. package/src/modules/input.ts +71 -0
  242. package/src/modules/led.ts +61 -0
  243. package/src/modules/location.ts +39 -0
  244. package/src/modules/mic.ts +74 -0
  245. package/src/modules/miniapps.ts +90 -0
  246. package/src/modules/navigation.ts +670 -0
  247. package/src/modules/permissions.ts +84 -0
  248. package/src/modules/phone.ts +155 -0
  249. package/src/modules/pivots/engine.ts +863 -0
  250. package/src/modules/pivots/geometry.ts +409 -0
  251. package/src/modules/pivots/instructions.ts +343 -0
  252. package/src/modules/speaker.ts +335 -0
  253. package/src/modules/storage.ts +109 -0
  254. package/src/modules/stream.ts +163 -0
  255. package/src/modules/system.ts +76 -0
  256. package/src/modules/transcription.ts +145 -0
  257. package/src/modules/translation.ts +149 -0
  258. package/src/modules/ui.ts +498 -0
  259. package/src/protocol.ts +378 -0
  260. package/src/react/MentraProvider.tsx +50 -0
  261. package/src/react/MiniappHeader.tsx +122 -0
  262. package/src/react/index.ts +12 -0
  263. package/src/react/useCapabilities.ts +29 -0
  264. package/src/react/useCapsuleHeaderStyle.ts +54 -0
  265. package/src/react/useColorScheme.ts +26 -0
  266. package/src/react/useConnected.ts +47 -0
  267. package/src/react/useRpc.ts +106 -0
  268. package/src/react/useSafeArea.ts +40 -0
  269. package/src/session.ts +886 -0
  270. package/src/sub-path-types.test-d.ts +49 -0
  271. package/src/transport/auto.ts +143 -0
  272. package/src/transport/dispatch.ts +106 -0
  273. package/src/transport/local-socket.ts +93 -0
  274. package/src/transport/mock.ts +251 -0
  275. package/src/transport/postmessage.ts +95 -0
  276. package/src/transport/types.ts +26 -0
  277. 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
+ }