@glassly/miniapp 0.1.0-dev.26

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