@glassly/engine 0.3.7 → 0.3.8-beta.984

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 (187) hide show
  1. package/README.md +4 -0
  2. package/build/engine.d.ts +8 -2
  3. package/build/engine.d.ts.map +1 -1
  4. package/build/engine.js +7 -4
  5. package/build/engine.js.map +1 -1
  6. package/build/facades/backup.d.ts +14 -0
  7. package/build/facades/backup.d.ts.map +1 -0
  8. package/build/facades/backup.js +22 -0
  9. package/build/facades/backup.js.map +1 -0
  10. package/build/facades/glasses.d.ts +4 -4
  11. package/build/facades/glasses.d.ts.map +1 -1
  12. package/build/facades/glasses.js +4 -4
  13. package/build/facades/glasses.js.map +1 -1
  14. package/build/generated/releaseMetadata.js +4 -4
  15. package/build/generated/releaseMetadata.js.map +1 -1
  16. package/build/index.d.ts +1 -0
  17. package/build/index.d.ts.map +1 -1
  18. package/build/index.js.map +1 -1
  19. package/build/services/AppRegistry.d.ts +2 -1
  20. package/build/services/AppRegistry.d.ts.map +1 -1
  21. package/build/services/AppRegistry.js +38 -8
  22. package/build/services/AppRegistry.js.map +1 -1
  23. package/build/services/AppStateBackup.d.ts +98 -0
  24. package/build/services/AppStateBackup.d.ts.map +1 -0
  25. package/build/services/AppStateBackup.js +224 -0
  26. package/build/services/AppStateBackup.js.map +1 -0
  27. package/build/services/BlobStore.d.ts.map +1 -1
  28. package/build/services/BlobStore.js +5 -12
  29. package/build/services/BlobStore.js.map +1 -1
  30. package/build/services/CloudClientService.d.ts +1 -0
  31. package/build/services/CloudClientService.d.ts.map +1 -1
  32. package/build/services/CloudClientService.js +34 -2
  33. package/build/services/CloudClientService.js.map +1 -1
  34. package/build/services/DisplayDiagnostics.d.ts.map +1 -1
  35. package/build/services/DisplayDiagnostics.js +4 -2
  36. package/build/services/DisplayDiagnostics.js.map +1 -1
  37. package/build/services/DisplayProcessor.d.ts +2 -0
  38. package/build/services/DisplayProcessor.d.ts.map +1 -1
  39. package/build/services/DisplayProcessor.js +15 -1
  40. package/build/services/DisplayProcessor.js.map +1 -1
  41. package/build/services/GlassesTouchInput.d.ts.map +1 -1
  42. package/build/services/GlassesTouchInput.js +4 -2
  43. package/build/services/GlassesTouchInput.js.map +1 -1
  44. package/build/services/JSCrashloopReportService.d.ts.map +1 -1
  45. package/build/services/JSCrashloopReportService.js +5 -0
  46. package/build/services/JSCrashloopReportService.js.map +1 -1
  47. package/build/services/JSRouter.d.ts +11 -3
  48. package/build/services/JSRouter.d.ts.map +1 -1
  49. package/build/services/JSRouter.js +36 -6
  50. package/build/services/JSRouter.js.map +1 -1
  51. package/build/services/LocalDisplayManager.d.ts +2 -0
  52. package/build/services/LocalDisplayManager.d.ts.map +1 -1
  53. package/build/services/LocalDisplayManager.js +4 -0
  54. package/build/services/LocalDisplayManager.js.map +1 -1
  55. package/build/services/LocalMiniappRuntime.d.ts +19 -0
  56. package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
  57. package/build/services/LocalMiniappRuntime.js +79 -22
  58. package/build/services/LocalMiniappRuntime.js.map +1 -1
  59. package/build/services/MiniappEngine.d.ts +9 -0
  60. package/build/services/MiniappEngine.d.ts.map +1 -1
  61. package/build/services/MiniappEngine.js +60 -27
  62. package/build/services/MiniappEngine.js.map +1 -1
  63. package/build/services/MiniappLauncher.d.ts +2 -0
  64. package/build/services/MiniappLauncher.d.ts.map +1 -1
  65. package/build/services/MiniappLauncher.js +6 -0
  66. package/build/services/MiniappLauncher.js.map +1 -1
  67. package/build/services/NavigationService.d.ts +1 -0
  68. package/build/services/NavigationService.d.ts.map +1 -1
  69. package/build/services/NavigationService.js +20 -6
  70. package/build/services/NavigationService.js.map +1 -1
  71. package/build/services/PhoneCalendarService.d.ts.map +1 -1
  72. package/build/services/PhoneCalendarService.js +8 -0
  73. package/build/services/PhoneCalendarService.js.map +1 -1
  74. package/build/services/PhoneNotificationsSync.d.ts.map +1 -1
  75. package/build/services/PhoneNotificationsSync.js +7 -3
  76. package/build/services/PhoneNotificationsSync.js.map +1 -1
  77. package/build/services/SceneRenderer.d.ts +8 -0
  78. package/build/services/SceneRenderer.d.ts.map +1 -1
  79. package/build/services/SceneRenderer.js +29 -1
  80. package/build/services/SceneRenderer.js.map +1 -1
  81. package/build/services/appStateBackupFile.d.ts +32 -0
  82. package/build/services/appStateBackupFile.d.ts.map +1 -0
  83. package/build/services/appStateBackupFile.js +135 -0
  84. package/build/services/appStateBackupFile.js.map +1 -0
  85. package/build/services/controllers.d.ts.map +1 -1
  86. package/build/services/controllers.js +11 -3
  87. package/build/services/controllers.js.map +1 -1
  88. package/build/services/effectiveCapabilities.js +3 -0
  89. package/build/services/effectiveCapabilities.js.map +1 -1
  90. package/build/services/pages/pageEligibility.d.ts.map +1 -1
  91. package/build/services/pages/pageEligibility.js +8 -1
  92. package/build/services/pages/pageEligibility.js.map +1 -1
  93. package/build/services/settingsBridge.d.ts.map +1 -1
  94. package/build/services/settingsBridge.js +1 -0
  95. package/build/services/settingsBridge.js.map +1 -1
  96. package/build/services/{appleWatch/AppleWatchLink.d.ts → watch/WatchLink.d.ts} +8 -7
  97. package/build/services/watch/WatchLink.d.ts.map +1 -0
  98. package/build/services/{appleWatch/AppleWatchLink.js → watch/WatchLink.js} +14 -15
  99. package/build/services/watch/WatchLink.js.map +1 -0
  100. package/build/stores/apps.d.ts.map +1 -1
  101. package/build/stores/apps.js +22 -10
  102. package/build/stores/apps.js.map +1 -1
  103. package/build/stores/settings.d.ts.map +1 -1
  104. package/build/stores/settings.js +56 -6
  105. package/build/stores/settings.js.map +1 -1
  106. package/build/types/capabilities/even-realities-g1.d.ts.map +1 -1
  107. package/build/types/capabilities/even-realities-g1.js +3 -0
  108. package/build/types/capabilities/even-realities-g1.js.map +1 -1
  109. package/build/types/capabilities/memomind-one.d.ts +3 -0
  110. package/build/types/capabilities/memomind-one.d.ts.map +1 -0
  111. package/build/types/capabilities/memomind-one.js +41 -0
  112. package/build/types/capabilities/memomind-one.js.map +1 -0
  113. package/build/types/enums.d.ts +6 -2
  114. package/build/types/enums.d.ts.map +1 -1
  115. package/build/types/enums.js +4 -0
  116. package/build/types/enums.js.map +1 -1
  117. package/build/types/hardware.d.ts +3 -1
  118. package/build/types/hardware.d.ts.map +1 -1
  119. package/build/types/hardware.js +3 -1
  120. package/build/types/hardware.js.map +1 -1
  121. package/build/utils/display/index.d.ts +2 -1
  122. package/build/utils/display/index.d.ts.map +1 -1
  123. package/build/utils/display/index.js +3 -2
  124. package/build/utils/display/index.js.map +1 -1
  125. package/build/utils/display/profiles/index.d.ts +2 -1
  126. package/build/utils/display/profiles/index.d.ts.map +1 -1
  127. package/build/utils/display/profiles/index.js +3 -2
  128. package/build/utils/display/profiles/index.js.map +1 -1
  129. package/build/utils/display/profiles/rayneo-io.d.ts +1 -1
  130. package/build/utils/display/profiles/rayneo-io.d.ts.map +1 -1
  131. package/build/utils/display/profiles/rayneo-io.js +1 -1
  132. package/build/utils/display/profiles/rayneo-io.js.map +1 -1
  133. package/build/utils/display/scene/index.d.ts +1 -1
  134. package/build/utils/display/scene/index.d.ts.map +1 -1
  135. package/build/utils/display/scene/index.js +1 -1
  136. package/build/utils/display/scene/index.js.map +1 -1
  137. package/build/utils/display/scene/process.d.ts +6 -0
  138. package/build/utils/display/scene/process.d.ts.map +1 -1
  139. package/build/utils/display/scene/process.js +22 -7
  140. package/build/utils/display/scene/process.js.map +1 -1
  141. package/build/utils/miniappGlobals.d.ts +13 -17
  142. package/build/utils/miniappGlobals.d.ts.map +1 -1
  143. package/build/utils/miniappGlobals.js +24 -56
  144. package/build/utils/miniappGlobals.js.map +1 -1
  145. package/package.json +7 -7
  146. package/src/engine.ts +7 -4
  147. package/src/facades/backup.ts +28 -0
  148. package/src/facades/glasses.ts +5 -5
  149. package/src/generated/releaseMetadata.ts +4 -4
  150. package/src/index.ts +1 -0
  151. package/src/services/AppRegistry.ts +36 -9
  152. package/src/services/AppStateBackup.ts +283 -0
  153. package/src/services/BlobStore.ts +5 -11
  154. package/src/services/CloudClientService.ts +35 -4
  155. package/src/services/DisplayDiagnostics.ts +4 -2
  156. package/src/services/DisplayProcessor.ts +20 -8
  157. package/src/services/GlassesTouchInput.ts +6 -2
  158. package/src/services/JSCrashloopReportService.ts +5 -0
  159. package/src/services/JSRouter.ts +37 -6
  160. package/src/services/LocalDisplayManager.ts +5 -0
  161. package/src/services/LocalMiniappRuntime.ts +81 -22
  162. package/src/services/MiniappEngine.ts +62 -27
  163. package/src/services/MiniappLauncher.ts +7 -0
  164. package/src/services/NavigationService.ts +28 -8
  165. package/src/services/PhoneCalendarService.ts +7 -0
  166. package/src/services/PhoneNotificationsSync.ts +8 -3
  167. package/src/services/SceneRenderer.ts +29 -1
  168. package/src/services/appStateBackupFile.ts +171 -0
  169. package/src/services/controllers.ts +12 -3
  170. package/src/services/effectiveCapabilities.ts +3 -0
  171. package/src/services/pages/pageEligibility.ts +9 -1
  172. package/src/services/settingsBridge.ts +1 -0
  173. package/src/services/{appleWatch/AppleWatchLink.ts → watch/WatchLink.ts} +20 -20
  174. package/src/stores/apps.ts +22 -11
  175. package/src/stores/settings.ts +63 -10
  176. package/src/types/capabilities/even-realities-g1.ts +4 -0
  177. package/src/types/capabilities/memomind-one.ts +42 -0
  178. package/src/types/enums.ts +4 -0
  179. package/src/types/hardware.ts +14 -1
  180. package/src/utils/display/index.ts +4 -2
  181. package/src/utils/display/profiles/index.ts +9 -2
  182. package/src/utils/display/profiles/rayneo-io.ts +6 -1
  183. package/src/utils/display/scene/index.ts +1 -1
  184. package/src/utils/display/scene/process.ts +22 -7
  185. package/src/utils/miniappGlobals.ts +32 -64
  186. package/build/services/appleWatch/AppleWatchLink.d.ts.map +0 -1
  187. package/build/services/appleWatch/AppleWatchLink.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"NavigationService.js","sourceRoot":"","sources":["../../src/services/NavigationService.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * NavigationService\n *\n * Thin singleton wrapper around the native Mapbox Navigation SDK exposed\n * by the `crust` Expo module.\n *\n * Mini apps will eventually subscribe through the mini app SDK, which\n * routes via LocalMiniappRuntime and ultimately calls into here. For now,\n * this is also called directly by the developer-settings test button.\n *\n * Works on both Android and iOS via the native Mapbox Navigation SDK.\n */\n\nimport CrustModule from \"@glassly/crust\"\n\nimport {resolveMapboxAccessToken, resolveMapboxRenderToken, userMapboxApiKey} from \"./MapboxTokenService\"\nimport {decodePolyline, parseDurationSeconds} from \"./navigation/routesApiCodec\"\nimport {resolveStepRoads} from \"./navigation/roadNameResolver\"\n\nconst LOG_TAG = \"NAV_SERVICE\"\n\nexport type NavManeuver = {\n kind: \"maneuver\"\n /**\n * Categorical type of the upcoming maneuver. One of: STRAIGHT,\n * SLIGHT_LEFT, SLIGHT_RIGHT, TURN_LEFT, TURN_RIGHT, SHARP_LEFT,\n * SHARP_RIGHT, U_TURN, ARRIVE.\n */\n maneuverType: string\n distanceMeters: number\n /** Road the user is currently on. Null if the SDK didn't supply one. */\n fromRoad?: string | null\n /** Road the user will be on after the maneuver. Null if the SDK didn't supply one. */\n toRoad?: string | null\n distanceToDestinationMeters?: number\n timeToDestinationSeconds?: number\n currentSpeedMps?: number | null\n speedLimitMps?: number | null\n routeHeadingDeg?: number | null\n}\n\nexport type NavOffRoute = {\n kind: \"off_route\"\n offRouteDistanceMeters: number\n}\n\nexport type NavRerouting = {kind: \"rerouting\"}\nexport type NavArrived = {kind: \"arrived\"}\nexport type NavError = {kind: \"error\"; message: string}\n\nexport type NavUpdate = NavManeuver | NavOffRoute | NavRerouting | NavArrived | NavError\n\nexport type NavListener = (update: NavUpdate) => void\n\nexport type NavLocation = {\n lat: number\n lng: number\n accuracy: number | null\n timestamp: number\n}\nexport type NavLocationListener = (loc: NavLocation) => void\n\nexport type NavRouteStep = {\n lat: number\n lng: number\n routeIndex: number\n road: string | null\n maneuver: string\n distanceMeters: number\n}\nexport type NavRoute = {\n points: Array<{lat: number; lng: number}>\n steps?: NavRouteStep[]\n}\nexport type NavRouteListener = (route: NavRoute) => void\n\nexport type NavState = \"idle\" | \"navigating\" | \"rerouting\" | \"arrived\"\n\n/**\n * Snapshot of the active trip — fed to miniapps that open mid-trip via\n * `navigation.getState()`. Mirrors the SDK's NavState shape closely; the\n * runtime keeps it strictly in sync as nav events arrive.\n */\nexport type NavTripSnapshot = {\n active: boolean\n mode?: string\n stops?: Array<{lat: number; lng: number}>\n currentStopIndex?: number\n route?: NavRoute\n maneuver?: NavManeuver\n distanceToDestinationMeters?: number\n timeToDestinationSeconds?: number\n currentSpeedMps?: number | null\n speedLimitMps?: number | null\n}\n\nclass NavigationService {\n private static instance: NavigationService | null = null\n private listeners = new Set<NavListener>()\n private locationListeners = new Set<NavLocationListener>()\n private routeListeners = new Set<NavRouteListener>()\n private subs: Array<{remove: () => void}> = []\n private state: NavState = \"idle\"\n /** Last emitted route — replayed to late subscribers so they get the\n * current geometry immediately. */\n private lastRoute: NavRoute | null = null\n /** Last maneuver event observed — used to hydrate getState() on mid-trip mount. */\n private lastManeuver: NavManeuver | null = null\n /** Trip configuration captured at start(), surfaced via getState(). */\n private tripStops: Array<{lat: number; lng: number}> = []\n private tripMode: string = \"driving\"\n\n private constructor() {}\n\n public static getInstance(): NavigationService {\n if (!NavigationService.instance) {\n NavigationService.instance = new NavigationService()\n }\n return NavigationService.instance\n }\n\n public getState(): NavState {\n return this.state\n }\n\n public addListener(listener: NavListener): () => void {\n this.listeners.add(listener)\n if (this.subs.length === 0) {\n this.attachNativeSubs()\n }\n return () => {\n this.listeners.delete(listener)\n if (this.noListeners()) {\n this.detachNativeSubs()\n }\n }\n }\n\n /**\n * Subscribe to the Mapbox Nav SDK's road-snapped location stream. Only\n * fires while a nav session is active. Returns an unsubscribe fn.\n */\n public addLocationListener(listener: NavLocationListener): () => void {\n this.locationListeners.add(listener)\n if (this.subs.length === 0) {\n this.attachNativeSubs()\n }\n return () => {\n this.locationListeners.delete(listener)\n if (this.noListeners()) {\n this.detachNativeSubs()\n }\n }\n }\n\n /**\n * Subscribe to the active route's polyline. Fires once per route build\n * (initial + after every reroute). Late subscribers get the current\n * route replayed on subscribe so the UI can render immediately.\n */\n public addRouteListener(listener: NavRouteListener): () => void {\n this.routeListeners.add(listener)\n if (this.subs.length === 0) {\n this.attachNativeSubs()\n }\n if (this.lastRoute) {\n try {\n listener(this.lastRoute)\n } catch (err) {\n console.error(`${LOG_TAG}: route listener threw on replay`, err)\n }\n }\n return () => {\n this.routeListeners.delete(listener)\n if (this.noListeners()) {\n this.detachNativeSubs()\n }\n }\n }\n\n private noListeners(): boolean {\n return this.listeners.size === 0 && this.locationListeners.size === 0 && this.routeListeners.size === 0\n }\n\n public async start(\n coords: {lat: number; lng: number},\n options?: {\n simulate?: boolean\n speedMultiplier?: number\n stops?: Array<{lat: number; lng: number}>\n mode?: string\n avoid?: {highways?: boolean; tolls?: boolean; ferries?: boolean}\n missedTurnRerouteMeters?: number\n },\n ): Promise<{ok: boolean; error?: string}> {\n console.log(\n `${LOG_TAG}: start ${coords.lat},${coords.lng} sim=${options?.simulate ?? false} speed=${options?.speedMultiplier ?? 5}`,\n )\n if (this.subs.length === 0) {\n // Listeners may attach after start(); make sure native subs exist\n // so we don't drop early events.\n this.attachNativeSubs()\n }\n // Resolve the token for the native SDK: user key first, else the public\n // render token served by Glassly Cloud. The binary bakes no key, so with\n // no resolvable token the trip must NOT start — the native SDK would\n // otherwise fail opaquely mid-boot. Cloud disabled + no user key lands\n // here by design. Pushed per-start rather than on settings change so it\n // can't race native boot.\n const mapboxRenderToken = await resolveMapboxRenderToken()\n if (!mapboxRenderToken) {\n const error =\n \"No Mapbox token available — add your key in Settings > API Keys\" +\n \" (required while Glassly Cloud is disabled or unreachable)\"\n console.warn(`${LOG_TAG}: start blocked — ${error}`)\n return {ok: false, error}\n }\n try {\n await CrustModule.setMapboxAccessToken(mapboxRenderToken)\n } catch (err) {\n // Native build predating setMapboxAccessToken — no token reaches the\n // SDK (nothing is baked), so fail the start rather than limp into an\n // opaque native error.\n console.warn(`${LOG_TAG}: setMapboxAccessToken unavailable`, err)\n return {ok: false, error: \"This build cannot receive a Mapbox token (update the app)\"}\n }\n const result = await CrustModule.startNavigation(coords.lat, coords.lng, {\n simulate: options?.simulate ?? false,\n speedMultiplier: options?.speedMultiplier ?? 5,\n stops: options?.stops,\n mode: options?.mode ?? \"driving\",\n avoid: options?.avoid,\n missedTurnRerouteMeters: options?.missedTurnRerouteMeters,\n })\n if (!result.ok) {\n console.warn(`${LOG_TAG}: start failed — ${result.error}`)\n this.state = \"idle\"\n this.tripStops = []\n this.tripMode = \"driving\"\n } else {\n this.state = \"navigating\"\n this.tripStops = options?.stops ?? [{lat: coords.lat, lng: coords.lng}]\n this.tripMode = options?.mode ?? \"driving\"\n }\n return result\n }\n\n /**\n * Snapshot of the active trip for `navigation.getState()`. Returns null\n * when no trip is running. The snapshot is built from in-memory state\n * fed by the existing native event stream — no extra IPC.\n */\n public getSnapshot(): NavTripSnapshot | null {\n if (this.state === \"idle\") return null\n const m = this.lastManeuver\n return {\n active: this.state !== \"arrived\",\n mode: this.tripMode,\n stops: this.tripStops.length > 0 ? this.tripStops : undefined,\n // Single-destination trips: index is always 0 (final stop). The Nav\n // SDK manages waypoint progression internally, so for now we report 0.\n currentStopIndex: 0,\n route: this.lastRoute ?? undefined,\n maneuver: m ?? undefined,\n distanceToDestinationMeters: m?.distanceToDestinationMeters,\n timeToDestinationSeconds: m?.timeToDestinationSeconds,\n currentSpeedMps: m?.currentSpeedMps,\n speedLimitMps: m?.speedLimitMps,\n }\n }\n\n public async stop(): Promise<{ok: boolean; error?: string}> {\n console.log(`${LOG_TAG}: stop`)\n const result = await CrustModule.stopNavigation()\n this.state = \"idle\"\n this.lastRoute = null\n this.lastManeuver = null\n this.tripStops = []\n this.tripMode = \"driving\"\n return result\n }\n\n /**\n * Request navigation permission. Under Mapbox there is no T&C dialog —\n * this resolves immediately with `accepted: true` — but it's still safe\n * (and intended) to call eagerly on mount.\n */\n public async requestPermission(): Promise<{ok: boolean; accepted: boolean; error?: string}> {\n console.log(`${LOG_TAG}: requestPermission`)\n return await CrustModule.requestNavigationPermission()\n }\n\n /**\n * Dev-only: nudge the simulator off-route to trigger an actual reroute\n * from the Nav SDK. Useful for verifying the rerouting pipeline (UI\n * flips to \"Rebuilding route…\", route polyline updates, glasses display\n * mirrors the new path). No-op on iOS or with real GPS fixes.\n */\n public async simulateDeviation(offsetMeters: number = 20): Promise<{ok: boolean; error?: string}> {\n console.log(`${LOG_TAG}: simulateDeviation(${offsetMeters}m)`)\n return await CrustModule.simulateDeviation(offsetMeters)\n }\n\n /**\n * Dev toggle: lock simulated locations onto the wrong sidewalk\n * (perpendicular-right of the route bearing by ~8m). Used to verify\n * the SDK's along-path pivot trigger fires even when the user never\n * comes within the 7m radius of a pivot point. Android-only today; iOS\n * is a no-op stub.\n */\n public async setWrongSidewalkOffset(enabled: boolean): Promise<{ok: boolean; error?: string}> {\n console.log(`${LOG_TAG}: setWrongSidewalkOffset(${enabled})`)\n return await CrustModule.setWrongSidewalkOffset(enabled)\n }\n\n /**\n * Dev toggle: take over from the Google simulator and walk the user\n * along a modified polyline that skips crossing micro-steps. Lets us\n * reproduce the wrong-sidewalk-then-missed-the-turn scenario for\n * pivot trigger testing. Android-only today; iOS is a no-op stub.\n */\n public async setSkipCrossings(enabled: boolean): Promise<{ok: boolean; error?: string}> {\n console.log(`${LOG_TAG}: setSkipCrossings(${enabled})`)\n return await CrustModule.setSkipCrossings(enabled)\n }\n\n /**\n * Compute one or more routes without starting a trip.\n *\n * Reached from the miniapp bridge (NavigationHandlers.handleComputeRoute)\n * when the user has saved their own Mapbox key in Settings > API Keys —\n * that key exists so navigation works without the cloud. Without a user\n * key the bridge uses the v2 cloud maps service, which holds a provider\n * token server-side and caches results. Implemented by\n * calling Mapbox's Directions API (REST) so we don't disturb the active\n * Navigator. Returns `{ok: false}` plus an error string when the engine\n * can't produce a route — mirrors the SDK's ComputeRouteResult shape so\n * the host can pass it back to miniapps unchanged.\n */\n public async computeRoute(payload: Record<string, unknown>): Promise<{\n ok: boolean\n error?: string\n routes?: Array<{\n points: Array<{lat: number; lng: number}>\n totalDistanceMeters: number\n totalDurationSeconds: number\n summary?: string\n }>\n }> {\n return computeRouteViaRoutesApi(payload)\n }\n\n /**\n * Reverse-geocode a coordinate into a short road/route name (plus the full\n * formatted `address` for dropped-pin / POI labels).\n *\n * Reached from the miniapp bridge when the user has saved their own Mapbox\n * key; otherwise the bridge uses the v2 cloud maps service. See\n * {@link computeRoute} for the same local-first branch.\n *\n * Implemented via Mapbox's Geocoding REST API. Backs the SDK pivot engine's\n * last-resort fallback when a Routes-API instruction didn't carry a parseable\n * road. Returns `{ok: true, road: null}` when the coordinate is genuinely\n * off-grid (mid-park, water) — that's a successful query with no road\n * component, distinct from a failure.\n */\n public async reverseGeocodeRoad(coord: {lat: number; lng: number}): Promise<{\n ok: boolean\n road?: string | null\n address?: string | null\n error?: string\n }> {\n return reverseGeocodeRoadViaGeocodingApi(coord)\n }\n\n /** True when a phone-side DIRECT Mapbox call can be made (user key saved). */\n public hasMapboxToken(): boolean {\n return !!resolveMapboxAccessToken()\n }\n\n /** True when the user saved their own Mapbox key in Settings > API Keys. */\n public hasUserMapboxKey(): boolean {\n return !!userMapboxApiKey()\n }\n\n /**\n * Type-ahead place search on-device with the user's own Mapbox key.\n * Preferred over the cloud maps service whenever that key is set — see\n * {@link placeAutocompleteViaSearchBox}.\n */\n public async placeAutocomplete(\n query: string,\n near: {lat: number; lng: number} | undefined,\n sessionToken: string,\n ): Promise<{\n ok: boolean\n suggestions?: Array<{placeId: string; mainText: string; secondaryText: string}>\n error?: string\n }> {\n return placeAutocompleteViaSearchBox(query, near, sessionToken)\n }\n\n /** Resolve a suggestion to coordinates on-device. Pairs with {@link placeAutocomplete}. */\n public async placeDetails(\n placeId: string,\n sessionToken: string,\n ): Promise<{\n ok: boolean\n place?: {placeId: string; name: string; address: string; lat: number; lng: number}\n error?: string\n }> {\n return placeDetailsViaSearchBox(placeId, sessionToken)\n }\n\n private attachNativeSubs(): void {\n console.log(`${LOG_TAG}: attachNativeSubs() — listeners=${this.listeners.size}`)\n this.subs.push(\n CrustModule.addListener(\"onNavManeuver\", (data) => {\n this.state = \"navigating\"\n const event: NavManeuver = {kind: \"maneuver\", ...data}\n this.lastManeuver = event\n this.fanout(event)\n }),\n CrustModule.addListener(\"onNavRerouting\", () => {\n console.log(`${LOG_TAG}: ← onNavRerouting`)\n this.state = \"rerouting\"\n this.fanout({kind: \"rerouting\"})\n }),\n CrustModule.addListener(\"onNavArrived\", () => {\n console.log(`${LOG_TAG}: ← onNavArrived`)\n this.state = \"arrived\"\n this.lastManeuver = null\n this.fanout({kind: \"arrived\"})\n }),\n CrustModule.addListener(\"onNavError\", (data) => {\n console.log(`${LOG_TAG}: ← onNavError`, data?.message)\n this.fanout({kind: \"error\", message: data.message})\n }),\n CrustModule.addListener(\"onNavLocation\", (data) => {\n const loc: NavLocation = {\n lat: data.lat,\n lng: data.lng,\n accuracy: data.accuracy ?? null,\n timestamp: data.timestamp,\n }\n this.locationListeners.forEach((l) => {\n try {\n l(loc)\n } catch (err) {\n console.error(`${LOG_TAG}: location listener threw`, err)\n }\n })\n }),\n CrustModule.addListener(\"onNavRoute\", (data) => {\n const route: NavRoute = {\n points: data.points ?? [],\n steps: data.steps?.map((step) => ({\n ...step,\n road: step.road ?? null,\n })),\n }\n this.lastRoute = route\n console.log(`${LOG_TAG}: ← onNavRoute (${route.points.length} points, steps=${route.steps?.length ?? \"null\"})`)\n this.routeListeners.forEach((l) => {\n try {\n l(route)\n } catch (err) {\n console.error(`${LOG_TAG}: route listener threw`, err)\n }\n })\n }),\n CrustModule.addListener(\"onNavOffRoute\", (data) => {\n console.log(`${LOG_TAG}: ← onNavOffRoute`, data?.offRouteDistanceMeters)\n this.fanout({kind: \"off_route\", offRouteDistanceMeters: data?.offRouteDistanceMeters ?? 0})\n }),\n )\n }\n\n private detachNativeSubs(): void {\n this.subs.forEach((s) => s.remove())\n this.subs = []\n }\n\n private fanout(update: NavUpdate): void {\n this.listeners.forEach((l) => {\n try {\n l(update)\n } catch (err) {\n console.error(`${LOG_TAG}: listener threw`, err)\n }\n })\n }\n}\n\nconst navigationService = NavigationService.getInstance()\nexport default navigationService\n\n// ---------------------------------------------------------------------------\n// Routes API (REST)\n// ---------------------------------------------------------------------------\n\n// Routes + Geocoding go DIRECT to Mapbox from the phone process ONLY when the\n// user saved their own key (Settings > API Keys) — \"use my account\". Without\n// one, callers (NavigationHandlers) route these operations through the v2\n// cloud maps proxy instead; the binary ships no baked Mapbox key.\nconst MAPBOX_DIRECTIONS_BASE = \"https://api.mapbox.com/directions/v5/mapbox\"\nconst MAPBOX_GEOCODE_REVERSE = \"https://api.mapbox.com/search/geocode/v6/reverse\"\nconst MAPBOX_SEARCHBOX_SUGGEST = \"https://api.mapbox.com/search/searchbox/v1/suggest\"\nconst MAPBOX_SEARCHBOX_RETRIEVE = \"https://api.mapbox.com/search/searchbox/v1/retrieve\"\n\n/**\n * The Mapbox token for phone-side REST calls: the user's key\n * (Settings > Keys) or nothing — empty means \"use the cloud maps proxy\".\n */\nfunction mapboxToken(): string {\n return resolveMapboxAccessToken()\n}\n\n/**\n * Phone-side helper: hit the Mapbox Directions API directly with the user's own\n * key (Settings > API Keys). Preferred over the v2 cloud maps service whenever\n * that key is set, so routing works without the cloud. Lives here\n * (not in Kotlin) so it's platform-independent across the JS bridge.\n * Returns the SDK-shaped result with primary + alternates.\n *\n * Mapbox Directions differs from Google Routes in shape: numeric\n * `distance`/`duration`, `polyline6` geometry per-route AND per-step, and\n * `maneuver = {location:[lng,lat], type, modifier, instruction}`. We\n * synthesize the SDK's `ComputedRouteStep` (lat/lng start, endLat/endLng\n * = next step's start) from the ordered step list.\n */\nasync function computeRouteViaRoutesApi(payload: Record<string, unknown>): Promise<{\n ok: boolean\n error?: string\n routes?: Array<{\n points: Array<{lat: number; lng: number}>\n totalDistanceMeters: number\n totalDurationSeconds: number\n summary?: string\n steps?: Array<{\n lat: number\n lng: number\n endLat: number\n endLng: number\n distanceMeters: number\n maneuver?: string\n instruction?: string\n road?: string | null\n }>\n }>\n}> {\n const origin = payload.origin as {lat?: unknown; lng?: unknown} | undefined\n const stopsRaw = payload.stops as Array<{lat?: unknown; lng?: unknown}> | undefined\n const mode = (typeof payload.mode === \"string\" ? payload.mode : \"driving\").toLowerCase()\n const alt = Number(payload.alternatives)\n const alternatives = Number.isFinite(alt) && alt > 0 ? alt : 1\n const avoid = (payload.avoid as Record<string, unknown> | undefined) ?? {}\n\n const oLat = Number(origin?.lat)\n const oLng = Number(origin?.lng)\n if (!Number.isFinite(oLat) || !Number.isFinite(oLng)) {\n return {ok: false, error: \"computeRoute: origin.lat/lng required\"}\n }\n const stops = (Array.isArray(stopsRaw) ? stopsRaw : [])\n .map((s) => ({lat: Number(s?.lat), lng: Number(s?.lng)}))\n .filter((s) => Number.isFinite(s.lat) && Number.isFinite(s.lng))\n if (stops.length === 0) {\n return {ok: false, error: \"computeRoute: at least one stop required\"}\n }\n\n const token = mapboxToken()\n if (!token) return {ok: false, error: \"computeRoute: no Mapbox key saved (add yours in Settings > API Keys)\"}\n\n // Mapbox path is a semicolon-joined `lng,lat` list: origin then each stop.\n const coords = [`${oLng},${oLat}`, ...stops.map((s) => `${s.lng},${s.lat}`)].join(\";\")\n const exclude = mapboxExclude(avoid)\n const params = new URLSearchParams({\n alternatives: String(alternatives > 1),\n geometries: \"polyline6\",\n overview: \"full\",\n steps: \"true\",\n banner_instructions: \"true\",\n voice_instructions: \"false\",\n access_token: token,\n })\n if (exclude) params.set(\"exclude\", exclude)\n const url = `${MAPBOX_DIRECTIONS_BASE}/${mapboxProfile(mode)}/${coords}?${params.toString()}`\n\n try {\n const res = await fetch(url, {method: \"GET\"})\n if (!res.ok) {\n return {ok: false, error: `Directions API ${res.status}`}\n }\n const json = (await res.json()) as {\n code?: string\n message?: string\n routes?: Array<{\n geometry?: string\n distance?: number\n duration?: string | number\n weight_name?: string\n legs?: Array<{\n summary?: string\n steps?: Array<{\n geometry?: string\n name?: string\n distance?: number\n maneuver?: {location?: [number, number]; type?: string; modifier?: string; instruction?: string}\n }>\n }>\n }>\n }\n if (json.code && json.code !== \"Ok\") {\n return {ok: false, error: json.message ?? `Directions code ${json.code}`}\n }\n // Resolve `road` for every step whose Mapbox `name` was blank (slip\n // lanes, unnamed paths, arrival legs) via reverse-geocode. Per-route\n // and per-alternate resolution fire concurrently.\n const routes = await Promise.all(\n (json.routes ?? []).slice(0, alternatives).map(async (r) => {\n const legs = r.legs ?? []\n const flatSteps = legs.flatMap((leg) => leg.steps ?? [])\n const rawSteps = flatSteps.map((s, i) => {\n const here = s.maneuver?.location // [lng, lat]\n const next = flatSteps[i + 1]?.maneuver?.location\n const lat = here?.[1] ?? Number.NaN\n const lng = here?.[0] ?? Number.NaN\n return {\n lat,\n lng,\n // End of step i == start of step i+1; final step ends at itself.\n endLat: next?.[1] ?? lat,\n endLng: next?.[0] ?? lng,\n distanceMeters: Math.round(s.distance ?? 0),\n maneuver: mapboxManeuverToKind(s.maneuver?.type, s.maneuver?.modifier),\n instruction: s.maneuver?.instruction,\n // Mapbox gives the road name inline as `step.name`; prefer it\n // over geocoding. Blank names fall through to resolveStepRoads.\n road: s.name && s.name.trim().length > 0 ? s.name.trim() : undefined,\n }\n })\n const steps = await resolveStepRoads(rawSteps, reverseGeocodeRoadViaGeocodingApi)\n return {\n points: decodePolyline(r.geometry ?? \"\", 6),\n totalDistanceMeters: Math.round(r.distance ?? 0),\n totalDurationSeconds: parseDurationSeconds(r.duration ?? 0),\n summary: legs[0]?.summary,\n steps: steps.length > 0 ? steps : undefined,\n }\n }),\n )\n if (routes.length === 0) return {ok: false, error: \"no routes returned\"}\n return {ok: true, routes}\n } catch (err) {\n return {ok: false, error: err instanceof Error ? err.message : \"computeRoute failed\"}\n }\n}\n\n/** SDK-agnostic mode → Mapbox Directions profile. two_wheeler → driving. */\nfunction mapboxProfile(mode: string): string {\n switch (mode) {\n case \"walking\":\n return \"walking\"\n case \"cycling\":\n return \"cycling\"\n case \"two_wheeler\":\n return \"driving\"\n default:\n return \"driving-traffic\"\n }\n}\n\n/** Avoid flags → Mapbox `exclude` param. Null when nothing to exclude. */\nfunction mapboxExclude(avoid: Record<string, unknown>): string | null {\n const ex: string[] = []\n if (avoid.tolls === true) ex.push(\"toll\")\n if (avoid.ferries === true) ex.push(\"ferry\")\n if (avoid.highways === true) ex.push(\"motorway\")\n return ex.length > 0 ? ex.join(\",\") : null\n}\n\n/**\n * Map Mapbox `maneuver.type` + `modifier` to the SDK's ManeuverKind\n * vocabulary — the JS-side mirror of NavigationManager.kt's mapManeuver\n * (issues/mapbox-navigation-migration.md §7). Kept here so the\n * computeRoute preview path labels turns the same way the live trip does.\n */\nfunction mapboxManeuverToKind(type?: string, modifier?: string): string | undefined {\n switch (type) {\n case \"depart\":\n return \"DEPART\"\n case \"arrive\":\n return \"ARRIVE\"\n case \"new name\":\n return \"NAME_CHANGE\"\n case \"continue\":\n case \"merge\":\n return \"CONTINUE\"\n case \"notification\":\n return \"STRAIGHT\"\n default:\n break\n }\n switch (modifier) {\n case \"uturn\":\n return \"U_TURN\"\n case \"sharp left\":\n return \"SHARP_LEFT\"\n case \"left\":\n return \"TURN_LEFT\"\n case \"slight left\":\n return \"SLIGHT_LEFT\"\n case \"straight\":\n return \"STRAIGHT\"\n case \"slight right\":\n return \"SLIGHT_RIGHT\"\n case \"right\":\n return \"TURN_RIGHT\"\n case \"sharp right\":\n return \"SHARP_RIGHT\"\n default:\n return type ? \"STRAIGHT\" : undefined\n }\n}\n\n/**\n * Reverse-geocode a coordinate via the Mapbox Geocoding v6 API directly, using\n * the user's own key (Settings > API Keys). Preferred over the v2 cloud maps\n * service whenever that key is set, so pin labels work without the cloud.\n *\n * Returns the road/street name (e.g. \"Octavia Blvd\") plus the full formatted\n * `address` for dropped-pin / POI labels. When no street is\n * present — the coordinate is genuinely off-grid (water, park interior) —\n * resolves with `{ok: true, road: null}`. Network failures and missing\n * token resolve with `{ok: false, error}`.\n *\n * Mapbox v6 returns a GeoJSON FeatureCollection; the street name lives at\n * `features[0].properties.context.street.name`. We also accept a feature\n * whose own `feature_type` is `street` (its `name` is the road). We prefer\n * the most-specific feature (the first), matching how the Google-era code\n * took the first `route` component.\n */\nasync function reverseGeocodeRoadViaGeocodingApi(coord: {lat: number; lng: number}): Promise<{\n ok: boolean\n road?: string | null\n address?: string | null\n error?: string\n}> {\n if (!Number.isFinite(coord.lat) || !Number.isFinite(coord.lng)) {\n return {ok: false, error: \"reverseGeocode: invalid coord\"}\n }\n const token = mapboxToken()\n if (!token)\n return {\n ok: false,\n error: \"reverseGeocode: no Mapbox key saved (add yours in Settings > API Keys)\",\n }\n const params = new URLSearchParams({\n longitude: String(coord.lng),\n latitude: String(coord.lat),\n // Bias toward address/street features so the street context is present.\n types: \"address,street\",\n limit: \"1\",\n access_token: token,\n })\n try {\n const res = await fetch(`${MAPBOX_GEOCODE_REVERSE}?${params.toString()}`, {method: \"GET\"})\n if (!res.ok) return {ok: false, error: `Geocoding API ${res.status}`}\n const json = (await res.json()) as {\n features?: Array<{\n properties?: {\n feature_type?: string\n name?: string\n full_address?: string\n place_formatted?: string\n context?: {street?: {name?: string}}\n }\n }>\n }\n // The full formatted address for dropped-pin / POI labels, taken from the\n // most-specific feature. Same precedence as the cloud provider so both\n // paths label a pin identically.\n const first = json.features?.[0]?.properties\n const address = (first?.full_address ?? first?.name ?? first?.place_formatted ?? \"\").trim() || null\n\n for (const feature of json.features ?? []) {\n const props = feature.properties\n // A street-type feature's own name is the road; otherwise read the\n // street from context.\n const fromStreetType = props?.feature_type === \"street\" ? props?.name : undefined\n const name = (fromStreetType ?? props?.context?.street?.name ?? \"\").trim()\n if (name) return {ok: true, road: name, address}\n }\n return {ok: true, road: null, address}\n } catch (err) {\n return {ok: false, error: err instanceof Error ? err.message : \"reverseGeocode failed\"}\n }\n}\n\n/**\n * Type-ahead place search straight from the device via Mapbox Search Box,\n * using the user's own key (Settings > API Keys). This is the offline-from-\n * the-cloud path: with a key saved, place search works with no runtime\n * involvement at all.\n *\n * Field extraction mirrors the cloud provider so both paths return identical\n * suggestions: only entries carrying a `mapbox_id` survive (without one there\n * is nothing to retrieve later), and the secondary line prefers\n * `place_formatted` over `full_address`.\n *\n * An empty query short-circuits to no suggestions — Search Box answers 422 for\n * a blank `q`. A missing token resolves `{ok: false}` so the caller can fall\n * back to the cloud.\n */\nasync function placeAutocompleteViaSearchBox(\n query: string,\n near: {lat: number; lng: number} | undefined,\n sessionToken: string,\n): Promise<{\n ok: boolean\n suggestions?: Array<{placeId: string; mainText: string; secondaryText: string}>\n error?: string\n}> {\n if (!query.trim()) return {ok: true, suggestions: []}\n const token = mapboxToken()\n if (!token) {\n return {\n ok: false,\n error: \"placeAutocomplete: no Mapbox key saved (add yours in Settings > API Keys)\",\n }\n }\n const params = new URLSearchParams({\n q: query,\n access_token: token,\n session_token: sessionToken,\n // POIs + addresses + places — the things a user navigates to.\n types: \"poi,address,place,street\",\n limit: \"10\",\n })\n // Mapbox wants lng,lat for proximity bias.\n if (near) params.set(\"proximity\", `${near.lng},${near.lat}`)\n\n try {\n const res = await fetch(`${MAPBOX_SEARCHBOX_SUGGEST}?${params.toString()}`, {method: \"GET\"})\n if (!res.ok) return {ok: false, error: `Search Box suggest ${res.status}`}\n const json = (await res.json()) as {\n suggestions?: Array<{\n mapbox_id?: string\n name?: string\n place_formatted?: string\n full_address?: string\n }>\n }\n const suggestions = (json.suggestions ?? [])\n .filter((s): s is {mapbox_id: string} & typeof s => !!s.mapbox_id)\n .map((s) => ({\n placeId: s.mapbox_id,\n mainText: s.name ?? \"\",\n secondaryText: s.place_formatted ?? s.full_address ?? \"\",\n }))\n return {ok: true, suggestions}\n } catch (err) {\n return {ok: false, error: err instanceof Error ? err.message : \"placeAutocomplete failed\"}\n }\n}\n\n/**\n * Resolve a Search Box suggestion to a full place with coordinates, using the\n * user's own key. Pairs with {@link placeAutocompleteViaSearchBox} — the same\n * `sessionToken` must thread both calls so Mapbox bills the keystrokes plus\n * this retrieve as one search session.\n *\n * Retrieve answers a GeoJSON FeatureCollection; coordinates live at\n * `features[0].geometry.coordinates` as [lng, lat].\n */\nasync function placeDetailsViaSearchBox(\n placeId: string,\n sessionToken: string,\n): Promise<{\n ok: boolean\n place?: {placeId: string; name: string; address: string; lat: number; lng: number}\n error?: string\n}> {\n const token = mapboxToken()\n if (!token) {\n return {\n ok: false,\n error: \"placeDetails: no Mapbox key saved (add yours in Settings > API Keys)\",\n }\n }\n const params = new URLSearchParams({access_token: token, session_token: sessionToken})\n try {\n const res = await fetch(`${MAPBOX_SEARCHBOX_RETRIEVE}/${encodeURIComponent(placeId)}?${params.toString()}`, {\n method: \"GET\",\n })\n if (!res.ok) return {ok: false, error: `Search Box retrieve ${res.status}`}\n const json = (await res.json()) as {\n features?: Array<{\n geometry?: {coordinates?: [number, number]}\n properties?: {\n name?: string\n full_address?: string\n place_formatted?: string\n }\n }>\n }\n const feature = json.features?.[0]\n const coords = feature?.geometry?.coordinates\n if (!coords || coords.length < 2 || !Number.isFinite(coords[0]) || !Number.isFinite(coords[1])) {\n return {ok: false, error: \"placeDetails: no coordinates for place\"}\n }\n const props = feature?.properties\n return {\n ok: true,\n place: {\n placeId,\n name: props?.name ?? \"\",\n address: props?.full_address ?? props?.place_formatted ?? \"\",\n lng: coords[0],\n lat: coords[1],\n },\n }\n } catch (err) {\n return {ok: false, error: err instanceof Error ? err.message : \"placeDetails failed\"}\n }\n}\n"]}
1
+ {"version":3,"file":"NavigationService.js","sourceRoot":"","sources":["../../src/services/NavigationService.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * NavigationService\n *\n * Thin singleton wrapper around the native Mapbox Navigation SDK exposed\n * by the `crust` Expo module.\n *\n * Mini apps will eventually subscribe through the mini app SDK, which\n * routes via LocalMiniappRuntime and ultimately calls into here. For now,\n * this is also called directly by the developer-settings test button.\n *\n * Works on both Android and iOS via the native Mapbox Navigation SDK.\n */\n\nimport CrustModule from \"@glassly/crust\"\n\nimport {resolveMapboxAccessToken, resolveMapboxRenderToken, userMapboxApiKey} from \"./MapboxTokenService\"\nimport {decodePolyline, parseDurationSeconds} from \"./navigation/routesApiCodec\"\nimport {resolveStepRoads} from \"./navigation/roadNameResolver\"\nimport {BgTimer} from \"../utils/timers\"\n\nconst LOG_TAG = \"NAV_SERVICE\"\n\nexport type NavManeuver = {\n kind: \"maneuver\"\n /**\n * Categorical type of the upcoming maneuver. One of: STRAIGHT,\n * SLIGHT_LEFT, SLIGHT_RIGHT, TURN_LEFT, TURN_RIGHT, SHARP_LEFT,\n * SHARP_RIGHT, U_TURN, ARRIVE.\n */\n maneuverType: string\n distanceMeters: number\n /** Road the user is currently on. Null if the SDK didn't supply one. */\n fromRoad?: string | null\n /** Road the user will be on after the maneuver. Null if the SDK didn't supply one. */\n toRoad?: string | null\n distanceToDestinationMeters?: number\n timeToDestinationSeconds?: number\n currentSpeedMps?: number | null\n speedLimitMps?: number | null\n routeHeadingDeg?: number | null\n}\n\nexport type NavOffRoute = {\n kind: \"off_route\"\n offRouteDistanceMeters: number\n}\n\nexport type NavRerouting = {kind: \"rerouting\"}\nexport type NavArrived = {kind: \"arrived\"}\nexport type NavError = {kind: \"error\"; message: string}\n\nexport type NavUpdate = NavManeuver | NavOffRoute | NavRerouting | NavArrived | NavError\n\nexport type NavListener = (update: NavUpdate) => void\n\nexport type NavLocation = {\n lat: number\n lng: number\n accuracy: number | null\n timestamp: number\n}\nexport type NavLocationListener = (loc: NavLocation) => void\n\nexport type NavRouteStep = {\n lat: number\n lng: number\n routeIndex: number\n road: string | null\n maneuver: string\n distanceMeters: number\n}\nexport type NavRoute = {\n points: Array<{lat: number; lng: number}>\n steps?: NavRouteStep[]\n}\nexport type NavRouteListener = (route: NavRoute) => void\n\nexport type NavState = \"idle\" | \"navigating\" | \"rerouting\" | \"arrived\"\n\n/**\n * Snapshot of the active trip — fed to miniapps that open mid-trip via\n * `navigation.getState()`. Mirrors the SDK's NavState shape closely; the\n * runtime keeps it strictly in sync as nav events arrive.\n */\nexport type NavTripSnapshot = {\n active: boolean\n mode?: string\n stops?: Array<{lat: number; lng: number}>\n currentStopIndex?: number\n route?: NavRoute\n maneuver?: NavManeuver\n distanceToDestinationMeters?: number\n timeToDestinationSeconds?: number\n currentSpeedMps?: number | null\n speedLimitMps?: number | null\n}\n\nclass NavigationService {\n private static instance: NavigationService | null = null\n private listeners = new Set<NavListener>()\n private locationListeners = new Set<NavLocationListener>()\n private routeListeners = new Set<NavRouteListener>()\n private subs: Array<{remove: () => void}> = []\n private state: NavState = \"idle\"\n /** Last emitted route — replayed to late subscribers so they get the\n * current geometry immediately. */\n private lastRoute: NavRoute | null = null\n /** Last maneuver event observed — used to hydrate getState() on mid-trip mount. */\n private lastManeuver: NavManeuver | null = null\n /** Trip configuration captured at start(), surfaced via getState(). */\n private tripStops: Array<{lat: number; lng: number}> = []\n private tripMode: string = \"driving\"\n\n private constructor() {}\n\n public static getInstance(): NavigationService {\n if (!NavigationService.instance) {\n NavigationService.instance = new NavigationService()\n }\n return NavigationService.instance\n }\n\n public getState(): NavState {\n return this.state\n }\n\n public addListener(listener: NavListener): () => void {\n this.listeners.add(listener)\n if (this.subs.length === 0) {\n this.attachNativeSubs()\n }\n return () => {\n this.listeners.delete(listener)\n if (this.noListeners()) {\n this.detachNativeSubs()\n }\n }\n }\n\n /**\n * Subscribe to the Mapbox Nav SDK's road-snapped location stream. Only\n * fires while a nav session is active. Returns an unsubscribe fn.\n */\n public addLocationListener(listener: NavLocationListener): () => void {\n this.locationListeners.add(listener)\n if (this.subs.length === 0) {\n this.attachNativeSubs()\n }\n return () => {\n this.locationListeners.delete(listener)\n if (this.noListeners()) {\n this.detachNativeSubs()\n }\n }\n }\n\n /**\n * Subscribe to the active route's polyline. Fires once per route build\n * (initial + after every reroute). Late subscribers get the current\n * route replayed on subscribe so the UI can render immediately.\n */\n public addRouteListener(listener: NavRouteListener): () => void {\n this.routeListeners.add(listener)\n if (this.subs.length === 0) {\n this.attachNativeSubs()\n }\n if (this.lastRoute) {\n try {\n listener(this.lastRoute)\n } catch (err) {\n console.error(`${LOG_TAG}: route listener threw on replay`, err)\n }\n }\n return () => {\n this.routeListeners.delete(listener)\n if (this.noListeners()) {\n this.detachNativeSubs()\n }\n }\n }\n\n private noListeners(): boolean {\n return this.listeners.size === 0 && this.locationListeners.size === 0 && this.routeListeners.size === 0\n }\n\n public async start(\n coords: {lat: number; lng: number},\n options?: {\n simulate?: boolean\n speedMultiplier?: number\n stops?: Array<{lat: number; lng: number}>\n mode?: string\n avoid?: {highways?: boolean; tolls?: boolean; ferries?: boolean}\n missedTurnRerouteMeters?: number\n },\n ): Promise<{ok: boolean; error?: string}> {\n console.log(\n `${LOG_TAG}: start ${coords.lat},${coords.lng} sim=${options?.simulate ?? false} speed=${options?.speedMultiplier ?? 5}`,\n )\n if (this.subs.length === 0) {\n // Listeners may attach after start(); make sure native subs exist\n // so we don't drop early events.\n this.attachNativeSubs()\n }\n // Resolve the token for the native SDK: user key first, else the public\n // render token served by Glassly Cloud. The binary bakes no key, so with\n // no resolvable token the trip must NOT start — the native SDK would\n // otherwise fail opaquely mid-boot. Cloud disabled + no user key lands\n // here by design. Pushed per-start rather than on settings change so it\n // can't race native boot.\n const mapboxRenderToken = await resolveMapboxRenderToken()\n if (!mapboxRenderToken) {\n const error =\n \"No Mapbox token available — add your key in Settings > API Keys\" +\n \" (required while Glassly Cloud is disabled or unreachable)\"\n console.warn(`${LOG_TAG}: start blocked — ${error}`)\n return {ok: false, error}\n }\n try {\n await CrustModule.setMapboxAccessToken(mapboxRenderToken)\n } catch (err) {\n // Native build predating setMapboxAccessToken — no token reaches the\n // SDK (nothing is baked), so fail the start rather than limp into an\n // opaque native error.\n console.warn(`${LOG_TAG}: setMapboxAccessToken unavailable`, err)\n return {ok: false, error: \"This build cannot receive a Mapbox token (update the app)\"}\n }\n const result = await CrustModule.startNavigation(coords.lat, coords.lng, {\n simulate: options?.simulate ?? false,\n speedMultiplier: options?.speedMultiplier ?? 5,\n stops: options?.stops,\n mode: options?.mode ?? \"driving\",\n avoid: options?.avoid,\n missedTurnRerouteMeters: options?.missedTurnRerouteMeters,\n })\n if (!result.ok) {\n console.warn(`${LOG_TAG}: start failed — ${result.error}`)\n this.state = \"idle\"\n this.tripStops = []\n this.tripMode = \"driving\"\n } else {\n this.state = \"navigating\"\n this.tripStops = options?.stops ?? [{lat: coords.lat, lng: coords.lng}]\n this.tripMode = options?.mode ?? \"driving\"\n }\n return result\n }\n\n /**\n * Snapshot of the active trip for `navigation.getState()`. Returns null\n * when no trip is running. The snapshot is built from in-memory state\n * fed by the existing native event stream — no extra IPC.\n */\n public getSnapshot(): NavTripSnapshot | null {\n if (this.state === \"idle\") return null\n const m = this.lastManeuver\n return {\n active: this.state !== \"arrived\",\n mode: this.tripMode,\n stops: this.tripStops.length > 0 ? this.tripStops : undefined,\n // Single-destination trips: index is always 0 (final stop). The Nav\n // SDK manages waypoint progression internally, so for now we report 0.\n currentStopIndex: 0,\n route: this.lastRoute ?? undefined,\n maneuver: m ?? undefined,\n distanceToDestinationMeters: m?.distanceToDestinationMeters,\n timeToDestinationSeconds: m?.timeToDestinationSeconds,\n currentSpeedMps: m?.currentSpeedMps,\n speedLimitMps: m?.speedLimitMps,\n }\n }\n\n public async stop(): Promise<{ok: boolean; error?: string}> {\n console.log(`${LOG_TAG}: stop`)\n const result = await CrustModule.stopNavigation()\n this.state = \"idle\"\n this.lastRoute = null\n this.lastManeuver = null\n this.tripStops = []\n this.tripMode = \"driving\"\n return result\n }\n\n /**\n * Request navigation permission. Under Mapbox there is no T&C dialog —\n * this resolves immediately with `accepted: true` — but it's still safe\n * (and intended) to call eagerly on mount.\n */\n public async requestPermission(): Promise<{ok: boolean; accepted: boolean; error?: string}> {\n console.log(`${LOG_TAG}: requestPermission`)\n return await CrustModule.requestNavigationPermission()\n }\n\n /**\n * Dev-only: nudge the simulator off-route to trigger an actual reroute\n * from the Nav SDK. Useful for verifying the rerouting pipeline (UI\n * flips to \"Rebuilding route…\", route polyline updates, glasses display\n * mirrors the new path). No-op on iOS or with real GPS fixes.\n */\n public async simulateDeviation(offsetMeters: number = 20): Promise<{ok: boolean; error?: string}> {\n console.log(`${LOG_TAG}: simulateDeviation(${offsetMeters}m)`)\n return await CrustModule.simulateDeviation(offsetMeters)\n }\n\n /**\n * Dev toggle: lock simulated locations onto the wrong sidewalk\n * (perpendicular-right of the route bearing by ~8m). Used to verify\n * the SDK's along-path pivot trigger fires even when the user never\n * comes within the 7m radius of a pivot point. Android-only today; iOS\n * is a no-op stub.\n */\n public async setWrongSidewalkOffset(enabled: boolean): Promise<{ok: boolean; error?: string}> {\n console.log(`${LOG_TAG}: setWrongSidewalkOffset(${enabled})`)\n return await CrustModule.setWrongSidewalkOffset(enabled)\n }\n\n /**\n * Dev toggle: take over from the Google simulator and walk the user\n * along a modified polyline that skips crossing micro-steps. Lets us\n * reproduce the wrong-sidewalk-then-missed-the-turn scenario for\n * pivot trigger testing. Android-only today; iOS is a no-op stub.\n */\n public async setSkipCrossings(enabled: boolean): Promise<{ok: boolean; error?: string}> {\n console.log(`${LOG_TAG}: setSkipCrossings(${enabled})`)\n return await CrustModule.setSkipCrossings(enabled)\n }\n\n /**\n * Compute one or more routes without starting a trip.\n *\n * Reached from the miniapp bridge (NavigationHandlers.handleComputeRoute)\n * when the user has saved their own Mapbox key in Settings > API Keys —\n * that key exists so navigation works without the cloud. Without a user\n * key the bridge uses the v2 cloud maps service, which holds a provider\n * token server-side and caches results. Implemented by\n * calling Mapbox's Directions API (REST) so we don't disturb the active\n * Navigator. Returns `{ok: false}` plus an error string when the engine\n * can't produce a route — mirrors the SDK's ComputeRouteResult shape so\n * the host can pass it back to miniapps unchanged.\n */\n public async computeRoute(payload: Record<string, unknown>): Promise<{\n ok: boolean\n error?: string\n routes?: Array<{\n points: Array<{lat: number; lng: number}>\n totalDistanceMeters: number\n totalDurationSeconds: number\n summary?: string\n }>\n }> {\n return computeRouteViaRoutesApi(payload)\n }\n\n /**\n * Reverse-geocode a coordinate into a short road/route name (plus the full\n * formatted `address` for dropped-pin / POI labels).\n *\n * Reached from the miniapp bridge when the user has saved their own Mapbox\n * key; otherwise the bridge uses the v2 cloud maps service. See\n * {@link computeRoute} for the same local-first branch.\n *\n * Implemented via Mapbox's Geocoding REST API. Backs the SDK pivot engine's\n * last-resort fallback when a Routes-API instruction didn't carry a parseable\n * road. Returns `{ok: true, road: null}` when the coordinate is genuinely\n * off-grid (mid-park, water) — that's a successful query with no road\n * component, distinct from a failure.\n */\n public async reverseGeocodeRoad(coord: {lat: number; lng: number}): Promise<{\n ok: boolean\n road?: string | null\n address?: string | null\n error?: string\n }> {\n return reverseGeocodeRoadViaGeocodingApi(coord)\n }\n\n /** True when a phone-side DIRECT Mapbox call can be made (user key saved). */\n public hasMapboxToken(): boolean {\n return !!resolveMapboxAccessToken()\n }\n\n /** True when the user saved their own Mapbox key in Settings > API Keys. */\n public hasUserMapboxKey(): boolean {\n return !!userMapboxApiKey()\n }\n\n /**\n * Type-ahead place search on-device with the user's own Mapbox key.\n * Preferred over the cloud maps service whenever that key is set — see\n * {@link placeAutocompleteViaSearchBox}.\n */\n public async placeAutocomplete(\n query: string,\n near: {lat: number; lng: number} | undefined,\n sessionToken: string,\n ): Promise<{\n ok: boolean\n suggestions?: Array<{placeId: string; mainText: string; secondaryText: string; distanceMeters?: number}>\n error?: string\n }> {\n return placeAutocompleteViaSearchBox(query, near, sessionToken)\n }\n\n /** Resolve a suggestion to coordinates on-device. Pairs with {@link placeAutocomplete}. */\n public async placeDetails(\n placeId: string,\n sessionToken: string,\n ): Promise<{\n ok: boolean\n place?: {placeId: string; name: string; address: string; lat: number; lng: number}\n error?: string\n }> {\n return placeDetailsViaSearchBox(placeId, sessionToken)\n }\n\n private attachNativeSubs(): void {\n console.log(`${LOG_TAG}: attachNativeSubs() — listeners=${this.listeners.size}`)\n this.subs.push(\n CrustModule.addListener(\"onNavManeuver\", (data) => {\n this.state = \"navigating\"\n const event: NavManeuver = {kind: \"maneuver\", ...data}\n this.lastManeuver = event\n this.fanout(event)\n }),\n CrustModule.addListener(\"onNavRerouting\", () => {\n console.log(`${LOG_TAG}: ← onNavRerouting`)\n this.state = \"rerouting\"\n this.fanout({kind: \"rerouting\"})\n }),\n CrustModule.addListener(\"onNavArrived\", () => {\n console.log(`${LOG_TAG}: ← onNavArrived`)\n this.state = \"arrived\"\n this.lastManeuver = null\n this.fanout({kind: \"arrived\"})\n }),\n CrustModule.addListener(\"onNavError\", (data) => {\n console.log(`${LOG_TAG}: ← onNavError`, data?.message)\n this.fanout({kind: \"error\", message: data.message})\n }),\n CrustModule.addListener(\"onNavLocation\", (data) => {\n const loc: NavLocation = {\n lat: data.lat,\n lng: data.lng,\n accuracy: data.accuracy ?? null,\n timestamp: data.timestamp,\n }\n this.locationListeners.forEach((l) => {\n try {\n l(loc)\n } catch (err) {\n console.error(`${LOG_TAG}: location listener threw`, err)\n }\n })\n }),\n CrustModule.addListener(\"onNavRoute\", (data) => {\n const route: NavRoute = {\n points: data.points ?? [],\n steps: data.steps?.map((step) => ({\n ...step,\n road: step.road ?? null,\n })),\n }\n this.lastRoute = route\n console.log(`${LOG_TAG}: ← onNavRoute (${route.points.length} points, steps=${route.steps?.length ?? \"null\"})`)\n this.routeListeners.forEach((l) => {\n try {\n l(route)\n } catch (err) {\n console.error(`${LOG_TAG}: route listener threw`, err)\n }\n })\n }),\n CrustModule.addListener(\"onNavOffRoute\", (data) => {\n console.log(`${LOG_TAG}: ← onNavOffRoute`, data?.offRouteDistanceMeters)\n this.fanout({kind: \"off_route\", offRouteDistanceMeters: data?.offRouteDistanceMeters ?? 0})\n }),\n )\n }\n\n private detachNativeSubs(): void {\n this.subs.forEach((s) => s.remove())\n this.subs = []\n }\n\n private fanout(update: NavUpdate): void {\n this.listeners.forEach((l) => {\n try {\n l(update)\n } catch (err) {\n console.error(`${LOG_TAG}: listener threw`, err)\n }\n })\n }\n}\n\nconst navigationService = NavigationService.getInstance()\nexport default navigationService\n\n// ---------------------------------------------------------------------------\n// Routes API (REST)\n// ---------------------------------------------------------------------------\n\n// Routes + Geocoding go DIRECT to Mapbox from the phone process ONLY when the\n// user saved their own key (Settings > API Keys) — \"use my account\". Without\n// one, callers (NavigationHandlers) route these operations through the v2\n// cloud maps proxy instead; the binary ships no baked Mapbox key.\nconst MAPBOX_DIRECTIONS_BASE = \"https://api.mapbox.com/directions/v5/mapbox\"\nconst MAPBOX_GEOCODE_REVERSE = \"https://api.mapbox.com/search/geocode/v6/reverse\"\nconst MAPBOX_SEARCHBOX_SUGGEST = \"https://api.mapbox.com/search/searchbox/v1/suggest\"\n\n/** Place search sits in front of a voice command; a stalled call must fail over, not hang. */\nconst PLACE_SEARCH_TIMEOUT_MS = 6_000\n\nasync function fetchWithTimeout(url: string, timeoutMs: number): Promise<Response> {\n const controller = new AbortController()\n const timer = BgTimer.setTimeout(() => controller.abort(), timeoutMs)\n try {\n return await fetch(url, {method: \"GET\", signal: controller.signal})\n } finally {\n BgTimer.clearTimeout(timer)\n }\n}\nconst MAPBOX_SEARCHBOX_RETRIEVE = \"https://api.mapbox.com/search/searchbox/v1/retrieve\"\n\n/**\n * The Mapbox token for phone-side REST calls: the user's key\n * (Settings > Keys) or nothing — empty means \"use the cloud maps proxy\".\n */\nfunction mapboxToken(): string {\n return resolveMapboxAccessToken()\n}\n\n/**\n * Phone-side helper: hit the Mapbox Directions API directly with the user's own\n * key (Settings > API Keys). Preferred over the v2 cloud maps service whenever\n * that key is set, so routing works without the cloud. Lives here\n * (not in Kotlin) so it's platform-independent across the JS bridge.\n * Returns the SDK-shaped result with primary + alternates.\n *\n * Mapbox Directions differs from Google Routes in shape: numeric\n * `distance`/`duration`, `polyline6` geometry per-route AND per-step, and\n * `maneuver = {location:[lng,lat], type, modifier, instruction}`. We\n * synthesize the SDK's `ComputedRouteStep` (lat/lng start, endLat/endLng\n * = next step's start) from the ordered step list.\n */\nasync function computeRouteViaRoutesApi(payload: Record<string, unknown>): Promise<{\n ok: boolean\n error?: string\n routes?: Array<{\n points: Array<{lat: number; lng: number}>\n totalDistanceMeters: number\n totalDurationSeconds: number\n summary?: string\n steps?: Array<{\n lat: number\n lng: number\n endLat: number\n endLng: number\n distanceMeters: number\n maneuver?: string\n instruction?: string\n road?: string | null\n }>\n }>\n}> {\n const origin = payload.origin as {lat?: unknown; lng?: unknown} | undefined\n const stopsRaw = payload.stops as Array<{lat?: unknown; lng?: unknown}> | undefined\n const mode = (typeof payload.mode === \"string\" ? payload.mode : \"driving\").toLowerCase()\n const alt = Number(payload.alternatives)\n const alternatives = Number.isFinite(alt) && alt > 0 ? alt : 1\n const avoid = (payload.avoid as Record<string, unknown> | undefined) ?? {}\n\n const oLat = Number(origin?.lat)\n const oLng = Number(origin?.lng)\n if (!Number.isFinite(oLat) || !Number.isFinite(oLng)) {\n return {ok: false, error: \"computeRoute: origin.lat/lng required\"}\n }\n const stops = (Array.isArray(stopsRaw) ? stopsRaw : [])\n .map((s) => ({lat: Number(s?.lat), lng: Number(s?.lng)}))\n .filter((s) => Number.isFinite(s.lat) && Number.isFinite(s.lng))\n if (stops.length === 0) {\n return {ok: false, error: \"computeRoute: at least one stop required\"}\n }\n\n const token = mapboxToken()\n if (!token) return {ok: false, error: \"computeRoute: no Mapbox key saved (add yours in Settings > API Keys)\"}\n\n // Mapbox path is a semicolon-joined `lng,lat` list: origin then each stop.\n const coords = [`${oLng},${oLat}`, ...stops.map((s) => `${s.lng},${s.lat}`)].join(\";\")\n const exclude = mapboxExclude(avoid)\n const params = new URLSearchParams({\n alternatives: String(alternatives > 1),\n geometries: \"polyline6\",\n overview: \"full\",\n steps: \"true\",\n banner_instructions: \"true\",\n voice_instructions: \"false\",\n access_token: token,\n })\n if (exclude) params.set(\"exclude\", exclude)\n const url = `${MAPBOX_DIRECTIONS_BASE}/${mapboxProfile(mode)}/${coords}?${params.toString()}`\n\n try {\n const res = await fetch(url, {method: \"GET\"})\n if (!res.ok) {\n return {ok: false, error: `Directions API ${res.status}`}\n }\n const json = (await res.json()) as {\n code?: string\n message?: string\n routes?: Array<{\n geometry?: string\n distance?: number\n duration?: string | number\n weight_name?: string\n legs?: Array<{\n summary?: string\n steps?: Array<{\n geometry?: string\n name?: string\n distance?: number\n maneuver?: {location?: [number, number]; type?: string; modifier?: string; instruction?: string}\n }>\n }>\n }>\n }\n if (json.code && json.code !== \"Ok\") {\n return {ok: false, error: json.message ?? `Directions code ${json.code}`}\n }\n // Resolve `road` for every step whose Mapbox `name` was blank (slip\n // lanes, unnamed paths, arrival legs) via reverse-geocode. Per-route\n // and per-alternate resolution fire concurrently.\n const routes = await Promise.all(\n (json.routes ?? []).slice(0, alternatives).map(async (r) => {\n const legs = r.legs ?? []\n const flatSteps = legs.flatMap((leg) => leg.steps ?? [])\n const rawSteps = flatSteps.map((s, i) => {\n const here = s.maneuver?.location // [lng, lat]\n const next = flatSteps[i + 1]?.maneuver?.location\n const lat = here?.[1] ?? Number.NaN\n const lng = here?.[0] ?? Number.NaN\n return {\n lat,\n lng,\n // End of step i == start of step i+1; final step ends at itself.\n endLat: next?.[1] ?? lat,\n endLng: next?.[0] ?? lng,\n distanceMeters: Math.round(s.distance ?? 0),\n maneuver: mapboxManeuverToKind(s.maneuver?.type, s.maneuver?.modifier),\n instruction: s.maneuver?.instruction,\n // Mapbox gives the road name inline as `step.name`; prefer it\n // over geocoding. Blank names fall through to resolveStepRoads.\n road: s.name && s.name.trim().length > 0 ? s.name.trim() : undefined,\n }\n })\n const steps = await resolveStepRoads(rawSteps, reverseGeocodeRoadViaGeocodingApi)\n return {\n points: decodePolyline(r.geometry ?? \"\", 6),\n totalDistanceMeters: Math.round(r.distance ?? 0),\n totalDurationSeconds: parseDurationSeconds(r.duration ?? 0),\n summary: legs[0]?.summary,\n steps: steps.length > 0 ? steps : undefined,\n }\n }),\n )\n if (routes.length === 0) return {ok: false, error: \"no routes returned\"}\n return {ok: true, routes}\n } catch (err) {\n return {ok: false, error: err instanceof Error ? err.message : \"computeRoute failed\"}\n }\n}\n\n/** SDK-agnostic mode → Mapbox Directions profile. two_wheeler → driving. */\nfunction mapboxProfile(mode: string): string {\n switch (mode) {\n case \"walking\":\n return \"walking\"\n case \"cycling\":\n return \"cycling\"\n case \"two_wheeler\":\n return \"driving\"\n default:\n return \"driving-traffic\"\n }\n}\n\n/** Avoid flags → Mapbox `exclude` param. Null when nothing to exclude. */\nfunction mapboxExclude(avoid: Record<string, unknown>): string | null {\n const ex: string[] = []\n if (avoid.tolls === true) ex.push(\"toll\")\n if (avoid.ferries === true) ex.push(\"ferry\")\n if (avoid.highways === true) ex.push(\"motorway\")\n return ex.length > 0 ? ex.join(\",\") : null\n}\n\n/**\n * Map Mapbox `maneuver.type` + `modifier` to the SDK's ManeuverKind\n * vocabulary — the JS-side mirror of NavigationManager.kt's mapManeuver\n * (issues/mapbox-navigation-migration.md §7). Kept here so the\n * computeRoute preview path labels turns the same way the live trip does.\n */\nfunction mapboxManeuverToKind(type?: string, modifier?: string): string | undefined {\n switch (type) {\n case \"depart\":\n return \"DEPART\"\n case \"arrive\":\n return \"ARRIVE\"\n case \"new name\":\n return \"NAME_CHANGE\"\n case \"continue\":\n case \"merge\":\n return \"CONTINUE\"\n case \"notification\":\n return \"STRAIGHT\"\n default:\n break\n }\n switch (modifier) {\n case \"uturn\":\n return \"U_TURN\"\n case \"sharp left\":\n return \"SHARP_LEFT\"\n case \"left\":\n return \"TURN_LEFT\"\n case \"slight left\":\n return \"SLIGHT_LEFT\"\n case \"straight\":\n return \"STRAIGHT\"\n case \"slight right\":\n return \"SLIGHT_RIGHT\"\n case \"right\":\n return \"TURN_RIGHT\"\n case \"sharp right\":\n return \"SHARP_RIGHT\"\n default:\n return type ? \"STRAIGHT\" : undefined\n }\n}\n\n/**\n * Reverse-geocode a coordinate via the Mapbox Geocoding v6 API directly, using\n * the user's own key (Settings > API Keys). Preferred over the v2 cloud maps\n * service whenever that key is set, so pin labels work without the cloud.\n *\n * Returns the road/street name (e.g. \"Octavia Blvd\") plus the full formatted\n * `address` for dropped-pin / POI labels. When no street is\n * present — the coordinate is genuinely off-grid (water, park interior) —\n * resolves with `{ok: true, road: null}`. Network failures and missing\n * token resolve with `{ok: false, error}`.\n *\n * Mapbox v6 returns a GeoJSON FeatureCollection; the street name lives at\n * `features[0].properties.context.street.name`. We also accept a feature\n * whose own `feature_type` is `street` (its `name` is the road). We prefer\n * the most-specific feature (the first), matching how the Google-era code\n * took the first `route` component.\n */\nasync function reverseGeocodeRoadViaGeocodingApi(coord: {lat: number; lng: number}): Promise<{\n ok: boolean\n road?: string | null\n address?: string | null\n error?: string\n}> {\n if (!Number.isFinite(coord.lat) || !Number.isFinite(coord.lng)) {\n return {ok: false, error: \"reverseGeocode: invalid coord\"}\n }\n const token = mapboxToken()\n if (!token)\n return {\n ok: false,\n error: \"reverseGeocode: no Mapbox key saved (add yours in Settings > API Keys)\",\n }\n const params = new URLSearchParams({\n longitude: String(coord.lng),\n latitude: String(coord.lat),\n // Bias toward address/street features so the street context is present.\n types: \"address,street\",\n limit: \"1\",\n access_token: token,\n })\n try {\n const res = await fetch(`${MAPBOX_GEOCODE_REVERSE}?${params.toString()}`, {method: \"GET\"})\n if (!res.ok) return {ok: false, error: `Geocoding API ${res.status}`}\n const json = (await res.json()) as {\n features?: Array<{\n properties?: {\n feature_type?: string\n name?: string\n full_address?: string\n place_formatted?: string\n context?: {street?: {name?: string}}\n }\n }>\n }\n // The full formatted address for dropped-pin / POI labels, taken from the\n // most-specific feature. Same precedence as the cloud provider so both\n // paths label a pin identically.\n const first = json.features?.[0]?.properties\n const address = (first?.full_address ?? first?.name ?? first?.place_formatted ?? \"\").trim() || null\n\n for (const feature of json.features ?? []) {\n const props = feature.properties\n // A street-type feature's own name is the road; otherwise read the\n // street from context.\n const fromStreetType = props?.feature_type === \"street\" ? props?.name : undefined\n const name = (fromStreetType ?? props?.context?.street?.name ?? \"\").trim()\n if (name) return {ok: true, road: name, address}\n }\n return {ok: true, road: null, address}\n } catch (err) {\n return {ok: false, error: err instanceof Error ? err.message : \"reverseGeocode failed\"}\n }\n}\n\n/**\n * Type-ahead place search straight from the device via Mapbox Search Box,\n * using the user's own key (Settings > API Keys). This is the offline-from-\n * the-cloud path: with a key saved, place search works with no runtime\n * involvement at all.\n *\n * Field extraction mirrors the cloud provider so both paths return identical\n * suggestions: only entries carrying a `mapbox_id` survive (without one there\n * is nothing to retrieve later), and the secondary line prefers\n * `place_formatted` over `full_address`.\n *\n * An empty query short-circuits to no suggestions — Search Box answers 422 for\n * a blank `q`. A missing token resolves `{ok: false}` so the caller can fall\n * back to the cloud.\n */\nasync function placeAutocompleteViaSearchBox(\n query: string,\n near: {lat: number; lng: number} | undefined,\n sessionToken: string,\n): Promise<{\n ok: boolean\n suggestions?: Array<{placeId: string; mainText: string; secondaryText: string; distanceMeters?: number}>\n error?: string\n}> {\n if (!query.trim()) return {ok: true, suggestions: []}\n const token = mapboxToken()\n if (!token) {\n return {\n ok: false,\n error: \"placeAutocomplete: no Mapbox key saved (add yours in Settings > API Keys)\",\n }\n }\n const params = new URLSearchParams({\n q: query,\n access_token: token,\n session_token: sessionToken,\n // POIs + addresses + places — the things a user navigates to.\n types: \"poi,address,place,street\",\n limit: \"10\",\n })\n // Mapbox wants lng,lat for proximity bias; `origin` adds per-suggestion distance.\n if (near) {\n params.set(\"proximity\", `${near.lng},${near.lat}`)\n params.set(\"origin\", `${near.lng},${near.lat}`)\n }\n\n try {\n const res = await fetchWithTimeout(`${MAPBOX_SEARCHBOX_SUGGEST}?${params.toString()}`, PLACE_SEARCH_TIMEOUT_MS)\n if (!res.ok) return {ok: false, error: `Search Box suggest ${res.status}`}\n const json = (await res.json()) as {\n suggestions?: Array<{\n mapbox_id?: string\n name?: string\n place_formatted?: string\n full_address?: string\n distance?: number\n }>\n }\n const suggestions = (json.suggestions ?? [])\n .filter((s): s is {mapbox_id: string} & typeof s => !!s.mapbox_id)\n .map((s) => ({\n placeId: s.mapbox_id,\n mainText: s.name ?? \"\",\n secondaryText: s.place_formatted ?? s.full_address ?? \"\",\n ...(typeof s.distance === \"number\" ? {distanceMeters: s.distance} : {}),\n }))\n return {ok: true, suggestions}\n } catch (err) {\n return {ok: false, error: err instanceof Error ? err.message : \"placeAutocomplete failed\"}\n }\n}\n\n/**\n * Resolve a Search Box suggestion to a full place with coordinates, using the\n * user's own key. Pairs with {@link placeAutocompleteViaSearchBox} — the same\n * `sessionToken` must thread both calls so Mapbox bills the keystrokes plus\n * this retrieve as one search session.\n *\n * Retrieve answers a GeoJSON FeatureCollection; coordinates live at\n * `features[0].geometry.coordinates` as [lng, lat].\n */\nasync function placeDetailsViaSearchBox(\n placeId: string,\n sessionToken: string,\n): Promise<{\n ok: boolean\n place?: {placeId: string; name: string; address: string; lat: number; lng: number}\n error?: string\n}> {\n const token = mapboxToken()\n if (!token) {\n return {\n ok: false,\n error: \"placeDetails: no Mapbox key saved (add yours in Settings > API Keys)\",\n }\n }\n const params = new URLSearchParams({access_token: token, session_token: sessionToken})\n try {\n const res = await fetchWithTimeout(\n `${MAPBOX_SEARCHBOX_RETRIEVE}/${encodeURIComponent(placeId)}?${params.toString()}`,\n PLACE_SEARCH_TIMEOUT_MS,\n )\n if (!res.ok) return {ok: false, error: `Search Box retrieve ${res.status}`}\n const json = (await res.json()) as {\n features?: Array<{\n geometry?: {coordinates?: [number, number]}\n properties?: {\n name?: string\n full_address?: string\n place_formatted?: string\n }\n }>\n }\n const feature = json.features?.[0]\n const coords = feature?.geometry?.coordinates\n if (!coords || coords.length < 2 || !Number.isFinite(coords[0]) || !Number.isFinite(coords[1])) {\n return {ok: false, error: \"placeDetails: no coordinates for place\"}\n }\n const props = feature?.properties\n return {\n ok: true,\n place: {\n placeId,\n name: props?.name ?? \"\",\n address: props?.full_address ?? props?.place_formatted ?? \"\",\n lng: coords[0],\n lat: coords[1],\n },\n }\n } catch (err) {\n return {ok: false, error: err instanceof Error ? err.message : \"placeDetails failed\"}\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"PhoneCalendarService.d.ts","sourceRoot":"","sources":["../../src/services/PhoneCalendarService.ts"],"names":[],"mappings":"AAIA,OAAO,EAAmB,KAAK,kBAAkB,EAAC,MAAM,kBAAkB,CAAA;AAE1E,OAAO,EAEL,KAAK,mBAAmB,EAIzB,MAAM,wBAAwB,CAAA;AAE/B,OAAO,EAAC,kBAAkB,EAAC,MAAM,wBAAwB,CAAA;AA0BzD,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAoBvG"}
1
+ {"version":3,"file":"PhoneCalendarService.d.ts","sourceRoot":"","sources":["../../src/services/PhoneCalendarService.ts"],"names":[],"mappings":"AAIA,OAAO,EAAmB,KAAK,kBAAkB,EAAC,MAAM,kBAAkB,CAAA;AAE1E,OAAO,EAEL,KAAK,mBAAmB,EAIzB,MAAM,wBAAwB,CAAA;AAE/B,OAAO,EAAC,kBAAkB,EAAC,MAAM,wBAAwB,CAAA;AA0BzD,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA2BvG"}
@@ -33,9 +33,17 @@ export async function listPhoneCalendarEvents(request) {
33
33
  if (calendars.length === 0)
34
34
  return { events: [], truncated: false };
35
35
  const rawEvents = await Calendar.listEvents(calendars, startsAt, endsAt);
36
+ const seen = new Set();
36
37
  const events = rawEvents
37
38
  .map(normalizeCalendarEvent)
38
39
  .filter((event) => calendarEventOverlapsWindow(event, startsAt, endsAt))
40
+ .filter((event) => {
41
+ const key = `${event.title}\n${event.startsAt}\n${event.endsAt}\n${event.allDay}`;
42
+ if (seen.has(key))
43
+ return false;
44
+ seen.add(key);
45
+ return true;
46
+ })
39
47
  .sort((a, b) => Date.parse(a.startsAt) - Date.parse(b.startsAt) || Date.parse(a.endsAt) - Date.parse(b.endsAt));
40
48
  return {
41
49
  events: events.slice(0, limit),
@@ -1 +1 @@
1
- {"version":3,"file":"PhoneCalendarService.js","sourceRoot":"","sources":["../../src/services/PhoneCalendarService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AACzC,OAAO,EAAC,QAAQ,EAAC,MAAM,cAAc,CAAA;AACrC,OAAO,EAAC,KAAK,EAAE,WAAW,EAAE,OAAO,EAAC,MAAM,0BAA0B,CAAA;AAEpE,OAAO,EAAC,gBAAgB,EAA0B,MAAM,kBAAkB,CAAA;AAE1E,OAAO,EACL,2BAA2B,EAE3B,sBAAsB,EACtB,kBAAkB,EAClB,2BAA2B,GAC5B,MAAM,wBAAwB,CAAA;AAE/B,OAAO,EAAC,kBAAkB,EAAC,MAAM,wBAAwB,CAAA;AAMzD;;;;;GAKG;AACH,KAAK,UAAU,yBAAyB,CAAC,MAAgC;IACvE,IAAI,MAAM,KAAK,cAAc,EAAE,CAAC;QAC9B,OAAO,6FAA6F,CAAA;IACtG,CAAC;IACD,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;QAC1B,MAAM,SAAS,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAA;QACrF,IAAI,SAAS,KAAK,OAAO,CAAC,OAAO,IAAI,SAAS,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC;YACnE,OAAO,0HAA0H,CAAA;QACnI,CAAC;QACD,OAAO,gGAAgG,CAAA;IACzG,CAAC;IACD,OAAO,qGAAqG,CAAA;AAC9G,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,OAA4B;IACxE,MAAM,EAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAC,GAAG,2BAA2B,CAAC,OAAO,CAAC,CAAA;IACtE,MAAM,UAAU,GAAG,MAAM,QAAQ,CAAC,sBAAsB,EAAE,CAAA;IAC1D,IAAI,UAAU,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QACpC,MAAM,IAAI,kBAAkB,CAAC,gBAAgB,CAAC,iBAAiB,EAAE,MAAM,yBAAyB,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAA;IACtH,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,YAAY,CAAC,QAAQ,CAAC,WAAW,CAAC,KAAK,CAAC,CAAA;IACzE,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAC,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,EAAC,CAAA;IAEjE,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,UAAU,CAAC,SAAS,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAA;IACxE,MAAM,MAAM,GAAG,SAAS;SACrB,GAAG,CAAC,sBAAsB,CAAC;SAC3B,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,2BAA2B,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;SACvE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAA;IAEjH,OAAO;QACL,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC;QAC9B,SAAS,EAAE,MAAM,CAAC,MAAM,GAAG,KAAK;KACjC,CAAA;AACH,CAAC","sourcesContent":["import * as Calendar from \"expo-calendar\"\nimport {Platform} from \"react-native\"\nimport {check, PERMISSIONS, RESULTS} from \"react-native-permissions\"\n\nimport {MiniappErrorCode, type CalendarListResult} from \"@glassly/miniapp\"\n\nimport {\n calendarEventOverlapsWindow,\n type CalendarListRequest,\n normalizeCalendarEvent,\n PhoneCalendarError,\n validateCalendarListRequest,\n} from \"./PhoneCalendarHelpers\"\n\nexport {PhoneCalendarError} from \"./PhoneCalendarHelpers\"\n\ntype CalendarPermissionStatus = Awaited<\n ReturnType<typeof Calendar.getCalendarPermissionsAsync>\n>[\"status\"]\n\n/**\n * expo-calendar reports anything below EventKit full access as not granted,\n * so distinguish the states the user can actually fix. \"Reopen the miniapp\"\n * is only true while the permission is undetermined — once iOS has resolved\n * it (denied or write-only/limited), only Settings can change it.\n */\nasync function calendarPermissionMessage(status: CalendarPermissionStatus): Promise<string> {\n if (status === \"undetermined\") {\n return \"Calendar permission has not been granted yet. Reopen the miniapp and allow calendar access.\"\n }\n if (Platform.OS === \"ios\") {\n const writeOnly = await check(PERMISSIONS.IOS.CALENDARS_WRITE_ONLY).catch(() => null)\n if (writeOnly === RESULTS.GRANTED || writeOnly === RESULTS.LIMITED) {\n return 'Calendar access is limited to write-only. Enable \"Full Access\" for Glassly in Settings › Privacy & Security › Calendars.'\n }\n return \"Calendar access is denied. Enable it for Glassly in Settings › Privacy & Security › Calendars.\"\n }\n return \"Calendar access is denied. Enable the Calendar permission for Glassly in your phone's app settings.\"\n}\n\nexport async function listPhoneCalendarEvents(request: CalendarListRequest): Promise<CalendarListResult> {\n const {startsAt, endsAt, limit} = validateCalendarListRequest(request)\n const permission = await Calendar.getCalendarPermissions()\n if (permission.status !== \"granted\") {\n throw new PhoneCalendarError(MiniappErrorCode.PERMISSION_DENIED, await calendarPermissionMessage(permission.status))\n }\n\n const calendars = await Calendar.getCalendars(Calendar.EntityTypes.EVENT)\n if (calendars.length === 0) return {events: [], truncated: false}\n\n const rawEvents = await Calendar.listEvents(calendars, startsAt, endsAt)\n const events = rawEvents\n .map(normalizeCalendarEvent)\n .filter((event) => calendarEventOverlapsWindow(event, startsAt, endsAt))\n .sort((a, b) => Date.parse(a.startsAt) - Date.parse(b.startsAt) || Date.parse(a.endsAt) - Date.parse(b.endsAt))\n\n return {\n events: events.slice(0, limit),\n truncated: events.length > limit,\n }\n}\n"]}
1
+ {"version":3,"file":"PhoneCalendarService.js","sourceRoot":"","sources":["../../src/services/PhoneCalendarService.ts"],"names":[],"mappings":"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","sourcesContent":["import * as Calendar from \"expo-calendar\"\nimport {Platform} from \"react-native\"\nimport {check, PERMISSIONS, RESULTS} from \"react-native-permissions\"\n\nimport {MiniappErrorCode, type CalendarListResult} from \"@glassly/miniapp\"\n\nimport {\n calendarEventOverlapsWindow,\n type CalendarListRequest,\n normalizeCalendarEvent,\n PhoneCalendarError,\n validateCalendarListRequest,\n} from \"./PhoneCalendarHelpers\"\n\nexport {PhoneCalendarError} from \"./PhoneCalendarHelpers\"\n\ntype CalendarPermissionStatus = Awaited<\n ReturnType<typeof Calendar.getCalendarPermissionsAsync>\n>[\"status\"]\n\n/**\n * expo-calendar reports anything below EventKit full access as not granted,\n * so distinguish the states the user can actually fix. \"Reopen the miniapp\"\n * is only true while the permission is undetermined — once iOS has resolved\n * it (denied or write-only/limited), only Settings can change it.\n */\nasync function calendarPermissionMessage(status: CalendarPermissionStatus): Promise<string> {\n if (status === \"undetermined\") {\n return \"Calendar permission has not been granted yet. Reopen the miniapp and allow calendar access.\"\n }\n if (Platform.OS === \"ios\") {\n const writeOnly = await check(PERMISSIONS.IOS.CALENDARS_WRITE_ONLY).catch(() => null)\n if (writeOnly === RESULTS.GRANTED || writeOnly === RESULTS.LIMITED) {\n return 'Calendar access is limited to write-only. Enable \"Full Access\" for Glassly in Settings › Privacy & Security › Calendars.'\n }\n return \"Calendar access is denied. Enable it for Glassly in Settings › Privacy & Security › Calendars.\"\n }\n return \"Calendar access is denied. Enable the Calendar permission for Glassly in your phone's app settings.\"\n}\n\nexport async function listPhoneCalendarEvents(request: CalendarListRequest): Promise<CalendarListResult> {\n const {startsAt, endsAt, limit} = validateCalendarListRequest(request)\n const permission = await Calendar.getCalendarPermissions()\n if (permission.status !== \"granted\") {\n throw new PhoneCalendarError(MiniappErrorCode.PERMISSION_DENIED, await calendarPermissionMessage(permission.status))\n }\n\n const calendars = await Calendar.getCalendars(Calendar.EntityTypes.EVENT)\n if (calendars.length === 0) return {events: [], truncated: false}\n\n const rawEvents = await Calendar.listEvents(calendars, startsAt, endsAt)\n const seen = new Set<string>()\n const events = rawEvents\n .map(normalizeCalendarEvent)\n .filter((event) => calendarEventOverlapsWindow(event, startsAt, endsAt))\n .filter((event) => {\n const key = `${event.title}\\n${event.startsAt}\\n${event.endsAt}\\n${event.allDay}`\n if (seen.has(key)) return false\n seen.add(key)\n return true\n })\n .sort((a, b) => Date.parse(a.startsAt) - Date.parse(b.startsAt) || Date.parse(a.endsAt) - Date.parse(b.endsAt))\n\n return {\n events: events.slice(0, limit),\n truncated: events.length > limit,\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"PhoneNotificationsSync.d.ts","sourceRoot":"","sources":["../../src/services/PhoneNotificationsSync.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,gBAAgB,CAAA;AAC1D,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,wBAAwB,CAAA;AAUrE,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACvB,MAAM,oCAAoC,CAAA;AAK3C,YAAY,EAAC,iBAAiB,EAAC,CAAA;AAE/B,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAID,wBAAgB,uBAAuB,IAAI,mBAAmB,EAAE,CAG/D;AAED,uEAAuE;AACvE,wBAAgB,wBAAwB,IAAI,eAAe,EAAE,CAS5D;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,IAAI,OAAO,CAClD,KAAK,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAC,CAAC,CACnE,CAMA;AAED,8EAA8E;AAC9E,wBAAgB,4BAA4B,IAAI,OAAO,CAGtD;AAED,gFAAgF;AAChF,wBAAgB,8BAA8B,IAAI,MAAM,EAAE,CAGzD;AAED,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C;AAED,wBAAgB,gCAAgC,IAAI,yBAAyB,CAa5E;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,OAAO,GAAG,iBAAiB,CAU/E;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,YAAY,CAMtF;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAapF;AAuCD,wBAAgB,2BAA2B,IAAI,IAAI,CAgClD;AAED,wBAAgB,0BAA0B,IAAI,IAAI,CAGjD"}
1
+ {"version":3,"file":"PhoneNotificationsSync.d.ts","sourceRoot":"","sources":["../../src/services/PhoneNotificationsSync.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,gBAAgB,CAAA;AAC1D,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,wBAAwB,CAAA;AAUrE,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACvB,MAAM,oCAAoC,CAAA;AAK3C,YAAY,EAAC,iBAAiB,EAAC,CAAA;AAE/B,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAID,wBAAgB,uBAAuB,IAAI,mBAAmB,EAAE,CAG/D;AAED,uEAAuE;AACvE,wBAAgB,wBAAwB,IAAI,eAAe,EAAE,CAS5D;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,IAAI,OAAO,CAClD,KAAK,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAC,CAAC,CACnE,CAMA;AAED,8EAA8E;AAC9E,wBAAgB,4BAA4B,IAAI,OAAO,CAGtD;AAOD,gFAAgF;AAChF,wBAAgB,8BAA8B,IAAI,MAAM,EAAE,CAGzD;AAED,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C;AAED,wBAAgB,gCAAgC,IAAI,yBAAyB,CAa5E;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,OAAO,GAAG,iBAAiB,CAU/E;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,YAAY,CAMtF;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAapF;AAuCD,wBAAgB,2BAA2B,IAAI,IAAI,CAgClD;AAED,wBAAgB,0BAA0B,IAAI,IAAI,CAGjD"}
@@ -67,6 +67,10 @@ export function nativeNotificationsSupported() {
67
67
  const model = useGlassesStore.getState().deviceModel;
68
68
  return getModelCapabilities(model).hasNativeNotifications === true;
69
69
  }
70
+ function notificationRelaySupported() {
71
+ const model = useGlassesStore.getState().deviceModel;
72
+ return Platform.OS === "ios" && getModelCapabilities(model).hasNotificationRelay === true;
73
+ }
70
74
  /** Popup durations the firmware accepts, or an empty list when it takes any. */
71
75
  export function nativeNotificationPopupSeconds() {
72
76
  const model = useGlassesStore.getState().deviceModel;
@@ -114,12 +118,12 @@ export function currentPresentationOwner(hostActive) {
114
118
  export function captureAuthorization() {
115
119
  if (Platform.OS !== "ios")
116
120
  return "not_needed";
117
- if (!nativeNotificationsSupported())
121
+ if (!nativeNotificationsSupported() && !notificationRelaySupported())
118
122
  return "not_needed";
119
123
  const glasses = useGlassesStore.getState();
120
124
  if (!isGlassesConnected(glasses.connection))
121
125
  return "unknown";
122
- return glasses.ancsAuthorized ? "granted" : "denied";
126
+ return glasses.ancsAuthorized ? "granted" : notificationRelaySupported() ? "unknown" : "denied";
123
127
  }
124
128
  /**
125
129
  * Hand one captured notification to glasses whose firmware presents it from
@@ -148,7 +152,7 @@ function pushListenerConfig() {
148
152
  CrustModule.setNotificationConfig(listenerEnabled, Array.isArray(blocklist) ? blocklist : []).catch((err) => console.warn(`PhoneNotificationsSync: setNotificationConfig failed: ${err?.message ?? err}`));
149
153
  }
150
154
  function pushGlassesConfig() {
151
- if (!nativeNotificationsSupported())
155
+ if (!nativeNotificationsSupported() && !notificationRelaySupported())
152
156
  return;
153
157
  if (!isGlassesConnected(useGlassesStore.getState().connection))
154
158
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"PhoneNotificationsSync.js","sourceRoot":"","sources":["../../src/services/PhoneNotificationsSync.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Phone-notifications sync — engine-owned, started by `engine.start()`. Keeps\n * three things in step with settings and the connected glasses, for any host:\n *\n * - Android: the notification listener's kill switch + per-app blocklist\n * (`CrustModule.setNotificationConfig`). The listener runs in its own\n * guarded process and is desired by default; the debug setting stays a\n * hidden emergency switch. Notify's running state gates presentation, not\n * capture.\n * - Glasses whose firmware presents notifications (`hasNativeNotifications`):\n * one `GlassesNotificationConfig` pushed through the Bluetooth SDK whenever\n * a setting, the phone's activity, the seen-apps list or the connected\n * glasses change. Drivers re-apply their last config on reconnect; the push\n * repeats on connection so a fresh driver gets it too.\n * - The seen-apps list (`notification_seen_apps`) from `notification_app_seen`:\n * glasses that read the phone's stream report each source app, which is\n * the only app enumeration iOS offers.\n *\n * Presentation is one question with one answer (`presentationOwner`); hosts\n * ask it per event instead of reasoning about models or platforms.\n */\nimport {Platform} from \"react-native\"\nimport {shallow} from \"zustand/shallow\"\nimport CrustModule from \"@glassly/crust\"\nimport type {PhoneNotificationEvent} from \"@glassly/crust\"\nimport type {GlassesNotificationConfig} from \"@glassly/bluetooth-sdk\"\nimport BluetoothSdk from \"@glassly/bluetooth-sdk/internal\"\nimport type {NotificationAppSeenEvent} from \"@glassly/bluetooth-sdk/internal\"\n\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {useSettingsStore, SETTINGS} from \"../stores/settings\"\nimport {DeviceTypes} from \"../types/enums\"\nimport {getModelCapabilities} from \"../types/hardware\"\nimport {isGlassesConnected} from \"./GlassesReadiness\"\nimport {notificationAppCatalog} from \"./notificationAppCatalog\"\nimport {\n glassesNotificationConfig,\n presentationOwner,\n type NotificationApp,\n type PresentationOwner,\n} from \"./notifications/notificationPolicy\"\nimport {overlayViewerRunning} from \"./pages/overlayApps\"\nimport {pagesEligible} from \"./pages/pageEligibility\"\nimport {isPhoneActive, onPhoneActivityChange} from \"./PhoneActivity\"\n\nexport type {PresentationOwner}\n\nexport interface SeenNotificationApp {\n appId: string\n displayName: string\n lastSeen: number\n}\n\nlet unsubscribers: Array<() => void> = []\n\nexport function getSeenNotificationApps(): SeenNotificationApp[] {\n const seen = useSettingsStore.getState().getSetting(SETTINGS.notification_seen_apps.key)\n return Array.isArray(seen) ? (seen as SeenNotificationApp[]) : []\n}\n\n/** Catalog ∪ seen apps, seen display names winning. Sorted by name. */\nexport function getKnownNotificationApps(): NotificationApp[] {\n const merged = new Map<string, string>()\n for (const app of notificationAppCatalog()) merged.set(app.appId, app.displayName)\n for (const app of getSeenNotificationApps()) {\n if (app.appId) merged.set(app.appId, app.displayName || app.appId)\n }\n return [...merged.entries()]\n .map(([appId, displayName]) => ({appId, displayName}))\n .sort((a, b) => a.displayName.localeCompare(b.displayName))\n}\n\n/**\n * Apps to show in a notification filter UI (package/bundle id, name, icon).\n * Android: installed apps. iOS: seeded catalog ∪ glasses-reported apps\n * (icons unavailable — iOS offers no cross-app icon lookup).\n */\nexport function installedNotificationApps(): Promise<\n Array<{packageName: string; appName: string; icon: string | null}>\n> {\n if (Platform.OS === \"android\") return CrustModule.getInstalledApps()\n if (Platform.OS !== \"ios\") return Promise.resolve([])\n return Promise.resolve(\n getKnownNotificationApps().map((app) => ({packageName: app.appId, appName: app.displayName, icon: null})),\n )\n}\n\n/** The connected (or remembered) model presents notifications in firmware. */\nexport function nativeNotificationsSupported(): boolean {\n const model = useGlassesStore.getState().deviceModel as DeviceTypes\n return getModelCapabilities(model).hasNativeNotifications === true\n}\n\n/** Popup durations the firmware accepts, or an empty list when it takes any. */\nexport function nativeNotificationPopupSeconds(): number[] {\n const model = useGlassesStore.getState().deviceModel as DeviceTypes\n return getModelCapabilities(model).nativeNotificationPopupSeconds ?? []\n}\n\nexport function notificationsEnabled(): boolean {\n return useSettingsStore.getState().getSetting(SETTINGS.notifications_enabled.key) !== false\n}\n\nexport function desiredGlassesNotificationConfig(): GlassesNotificationConfig {\n const s = useSettingsStore.getState()\n const blocklist = s.getSetting(SETTINGS.notifications_blocklist.key)\n return glassesNotificationConfig({\n enabled: notificationsEnabled(),\n popups: s.getSetting(SETTINGS.notifications_popups.key) !== false,\n popupSeconds: Number(s.getSetting(SETTINGS.notifications_popup_seconds.key)),\n holdDuringApps: s.getSetting(SETTINGS.notifications_hold_during_apps.key) !== false,\n ignoreWhileActive: s.getSetting(SETTINGS.notifications_ignore_while_active.key) === true,\n phoneActive: isPhoneActive(),\n blockedApps: Array.isArray(blocklist) ? (blocklist as string[]) : [],\n knownApps: getKnownNotificationApps(),\n })\n}\n\n/**\n * Who presents the next phone notification. `hostActive` is the host's own\n * presentation surface (Notify in the Glassly App); the engine has no view of\n * it.\n */\nexport function currentPresentationOwner(hostActive: boolean): PresentationOwner {\n return presentationOwner({\n enabled: notificationsEnabled(),\n pagesEligible: pagesEligible(),\n // The viewer is an overlay miniapp listening to phone notifications\n // (ADR-0006): with none running, nothing draws one on eligible glasses.\n viewerRunning: overlayViewerRunning(),\n nativeNotifications: nativeNotificationsSupported() && isGlassesConnected(useGlassesStore.getState().connection),\n hostActive,\n })\n}\n\n/**\n * Whether the glasses may read the phone's notifications. iOS glasses read\n * the stream themselves under the Bluetooth notification-sharing consent the\n * driver reports as `ancsAuthorized`; Android glasses get content from the\n * phone's listener, whose grant the permission flow owns.\n */\nexport function captureAuthorization(): \"granted\" | \"denied\" | \"unknown\" | \"not_needed\" {\n if (Platform.OS !== \"ios\") return \"not_needed\"\n if (!nativeNotificationsSupported()) return \"not_needed\"\n const glasses = useGlassesStore.getState()\n if (!isGlassesConnected(glasses.connection)) return \"unknown\"\n return glasses.ancsAuthorized ? \"granted\" : \"denied\"\n}\n\n/**\n * Hand one captured notification to glasses whose firmware presents it from\n * uploaded content (Android). Glasses that read the phone's stream reject it;\n * callers only reach here when the owner is `firmware`.\n */\nexport function uploadPhoneNotification(event: PhoneNotificationEvent): Promise<void> {\n if (Platform.OS !== \"android\") return Promise.resolve()\n return Promise.resolve(\n BluetoothSdk.uploadPhoneNotification({\n notificationId: String(event.notificationId ?? \"\"),\n packageName: String(event.packageName ?? \"\"),\n appName: String(event.app ?? \"\"),\n title: String(event.title ?? \"\"),\n subtitle: \"\",\n body: String(event.content ?? \"\"),\n timestampMs: Number(event.timestamp) || Date.now(),\n }),\n )\n}\n\nfunction pushListenerConfig(): void {\n if (Platform.OS !== \"android\") return\n const s = useSettingsStore.getState()\n const listenerEnabled = Boolean(s.getSetting(SETTINGS.android_notification_listener_enabled.key))\n const blocklist = s.getSetting(SETTINGS.notifications_blocklist.key)\n CrustModule.setNotificationConfig(listenerEnabled, Array.isArray(blocklist) ? blocklist : []).catch((err: unknown) =>\n console.warn(`PhoneNotificationsSync: setNotificationConfig failed: ${(err as Error)?.message ?? err}`),\n )\n}\n\nfunction pushGlassesConfig(): void {\n if (!nativeNotificationsSupported()) return\n if (!isGlassesConnected(useGlassesStore.getState().connection)) return\n void Promise.resolve(BluetoothSdk.setGlassesNotificationConfig(desiredGlassesNotificationConfig())).catch(\n (err: unknown) =>\n console.warn(`PhoneNotificationsSync: glasses notification config failed: ${(err as Error)?.message ?? err}`),\n )\n}\n\nfunction pushAll(): void {\n pushListenerConfig()\n pushGlassesConfig()\n}\n\n/** Record a glasses-reported source app. Writes only on new app / renamed app. */\nfunction recordSeenApp(event: NotificationAppSeenEvent): void {\n if (!event.appId) return\n const seen = getSeenNotificationApps()\n const existing = seen.find((app) => app.appId === event.appId)\n const displayName = event.displayName || existing?.displayName || event.appId\n if (existing && existing.displayName === displayName) return\n const next = seen\n .filter((app) => app.appId !== event.appId)\n .concat([{appId: event.appId, displayName, lastSeen: event.timestamp || Date.now()}])\n useSettingsStore.getState().setSetting(SETTINGS.notification_seen_apps.key, next)\n}\n\nexport function startPhoneNotificationsSync(): void {\n if (unsubscribers.length) return\n pushAll()\n const seenApps = BluetoothSdk.addListener(\"notification_app_seen\", recordSeenApp)\n unsubscribers = [\n useSettingsStore.subscribe(\n (state) =>\n [\n SETTINGS.android_notification_listener_enabled,\n SETTINGS.notifications_blocklist,\n SETTINGS.notification_seen_apps,\n SETTINGS.notifications_enabled,\n SETTINGS.notifications_popups,\n SETTINGS.notifications_popup_seconds,\n SETTINGS.notifications_hold_during_apps,\n SETTINGS.notifications_ignore_while_active,\n ].map((setting) => state.getSetting(setting.key)),\n pushAll,\n {equalityFn: shallow},\n ),\n // A (re)connected driver gets the current config; the SDK dedupes nothing\n // here, so only connection state and model are watched, not every field.\n useGlassesStore.subscribe(\n (state) => ({model: state.deviceModel, connected: isGlassesConnected(state.connection)}),\n pushGlassesConfig,\n {equalityFn: shallow},\n ),\n // Lock / foreground changes flip the effective popups flag for firmware\n // that pops notifications up by itself.\n onPhoneActivityChange(pushGlassesConfig),\n () => seenApps.remove(),\n ]\n}\n\nexport function stopPhoneNotificationsSync(): void {\n for (const unsubscribe of unsubscribers) unsubscribe()\n unsubscribers = []\n}\n"]}
1
+ {"version":3,"file":"PhoneNotificationsSync.js","sourceRoot":"","sources":["../../src/services/PhoneNotificationsSync.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Phone-notifications sync — engine-owned, started by `engine.start()`. Keeps\n * three things in step with settings and the connected glasses, for any host:\n *\n * - Android: the notification listener's kill switch + per-app blocklist\n * (`CrustModule.setNotificationConfig`). The listener runs in its own\n * guarded process and is desired by default; the debug setting stays a\n * hidden emergency switch. Notify's running state gates presentation, not\n * capture.\n * - Glasses whose firmware presents notifications (`hasNativeNotifications`):\n * one `GlassesNotificationConfig` pushed through the Bluetooth SDK whenever\n * a setting, the phone's activity, the seen-apps list or the connected\n * glasses change. Drivers re-apply their last config on reconnect; the push\n * repeats on connection so a fresh driver gets it too.\n * - The seen-apps list (`notification_seen_apps`) from `notification_app_seen`:\n * glasses that read the phone's stream report each source app, which is\n * the only app enumeration iOS offers.\n *\n * Presentation is one question with one answer (`presentationOwner`); hosts\n * ask it per event instead of reasoning about models or platforms.\n */\nimport {Platform} from \"react-native\"\nimport {shallow} from \"zustand/shallow\"\nimport CrustModule from \"@glassly/crust\"\nimport type {PhoneNotificationEvent} from \"@glassly/crust\"\nimport type {GlassesNotificationConfig} from \"@glassly/bluetooth-sdk\"\nimport BluetoothSdk from \"@glassly/bluetooth-sdk/internal\"\nimport type {NotificationAppSeenEvent} from \"@glassly/bluetooth-sdk/internal\"\n\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {useSettingsStore, SETTINGS} from \"../stores/settings\"\nimport {DeviceTypes} from \"../types/enums\"\nimport {getModelCapabilities} from \"../types/hardware\"\nimport {isGlassesConnected} from \"./GlassesReadiness\"\nimport {notificationAppCatalog} from \"./notificationAppCatalog\"\nimport {\n glassesNotificationConfig,\n presentationOwner,\n type NotificationApp,\n type PresentationOwner,\n} from \"./notifications/notificationPolicy\"\nimport {overlayViewerRunning} from \"./pages/overlayApps\"\nimport {pagesEligible} from \"./pages/pageEligibility\"\nimport {isPhoneActive, onPhoneActivityChange} from \"./PhoneActivity\"\n\nexport type {PresentationOwner}\n\nexport interface SeenNotificationApp {\n appId: string\n displayName: string\n lastSeen: number\n}\n\nlet unsubscribers: Array<() => void> = []\n\nexport function getSeenNotificationApps(): SeenNotificationApp[] {\n const seen = useSettingsStore.getState().getSetting(SETTINGS.notification_seen_apps.key)\n return Array.isArray(seen) ? (seen as SeenNotificationApp[]) : []\n}\n\n/** Catalog ∪ seen apps, seen display names winning. Sorted by name. */\nexport function getKnownNotificationApps(): NotificationApp[] {\n const merged = new Map<string, string>()\n for (const app of notificationAppCatalog()) merged.set(app.appId, app.displayName)\n for (const app of getSeenNotificationApps()) {\n if (app.appId) merged.set(app.appId, app.displayName || app.appId)\n }\n return [...merged.entries()]\n .map(([appId, displayName]) => ({appId, displayName}))\n .sort((a, b) => a.displayName.localeCompare(b.displayName))\n}\n\n/**\n * Apps to show in a notification filter UI (package/bundle id, name, icon).\n * Android: installed apps. iOS: seeded catalog ∪ glasses-reported apps\n * (icons unavailable — iOS offers no cross-app icon lookup).\n */\nexport function installedNotificationApps(): Promise<\n Array<{packageName: string; appName: string; icon: string | null}>\n> {\n if (Platform.OS === \"android\") return CrustModule.getInstalledApps()\n if (Platform.OS !== \"ios\") return Promise.resolve([])\n return Promise.resolve(\n getKnownNotificationApps().map((app) => ({packageName: app.appId, appName: app.displayName, icon: null})),\n )\n}\n\n/** The connected (or remembered) model presents notifications in firmware. */\nexport function nativeNotificationsSupported(): boolean {\n const model = useGlassesStore.getState().deviceModel as DeviceTypes\n return getModelCapabilities(model).hasNativeNotifications === true\n}\n\nfunction notificationRelaySupported(): boolean {\n const model = useGlassesStore.getState().deviceModel as DeviceTypes\n return Platform.OS === \"ios\" && getModelCapabilities(model).hasNotificationRelay === true\n}\n\n/** Popup durations the firmware accepts, or an empty list when it takes any. */\nexport function nativeNotificationPopupSeconds(): number[] {\n const model = useGlassesStore.getState().deviceModel as DeviceTypes\n return getModelCapabilities(model).nativeNotificationPopupSeconds ?? []\n}\n\nexport function notificationsEnabled(): boolean {\n return useSettingsStore.getState().getSetting(SETTINGS.notifications_enabled.key) !== false\n}\n\nexport function desiredGlassesNotificationConfig(): GlassesNotificationConfig {\n const s = useSettingsStore.getState()\n const blocklist = s.getSetting(SETTINGS.notifications_blocklist.key)\n return glassesNotificationConfig({\n enabled: notificationsEnabled(),\n popups: s.getSetting(SETTINGS.notifications_popups.key) !== false,\n popupSeconds: Number(s.getSetting(SETTINGS.notifications_popup_seconds.key)),\n holdDuringApps: s.getSetting(SETTINGS.notifications_hold_during_apps.key) !== false,\n ignoreWhileActive: s.getSetting(SETTINGS.notifications_ignore_while_active.key) === true,\n phoneActive: isPhoneActive(),\n blockedApps: Array.isArray(blocklist) ? (blocklist as string[]) : [],\n knownApps: getKnownNotificationApps(),\n })\n}\n\n/**\n * Who presents the next phone notification. `hostActive` is the host's own\n * presentation surface (Notify in the Glassly App); the engine has no view of\n * it.\n */\nexport function currentPresentationOwner(hostActive: boolean): PresentationOwner {\n return presentationOwner({\n enabled: notificationsEnabled(),\n pagesEligible: pagesEligible(),\n // The viewer is an overlay miniapp listening to phone notifications\n // (ADR-0006): with none running, nothing draws one on eligible glasses.\n viewerRunning: overlayViewerRunning(),\n nativeNotifications: nativeNotificationsSupported() && isGlassesConnected(useGlassesStore.getState().connection),\n hostActive,\n })\n}\n\n/**\n * Whether the glasses may read the phone's notifications. iOS glasses read\n * the stream themselves under the Bluetooth notification-sharing consent the\n * driver reports as `ancsAuthorized`; Android glasses get content from the\n * phone's listener, whose grant the permission flow owns.\n */\nexport function captureAuthorization(): \"granted\" | \"denied\" | \"unknown\" | \"not_needed\" {\n if (Platform.OS !== \"ios\") return \"not_needed\"\n if (!nativeNotificationsSupported() && !notificationRelaySupported()) return \"not_needed\"\n const glasses = useGlassesStore.getState()\n if (!isGlassesConnected(glasses.connection)) return \"unknown\"\n return glasses.ancsAuthorized ? \"granted\" : notificationRelaySupported() ? \"unknown\" : \"denied\"\n}\n\n/**\n * Hand one captured notification to glasses whose firmware presents it from\n * uploaded content (Android). Glasses that read the phone's stream reject it;\n * callers only reach here when the owner is `firmware`.\n */\nexport function uploadPhoneNotification(event: PhoneNotificationEvent): Promise<void> {\n if (Platform.OS !== \"android\") return Promise.resolve()\n return Promise.resolve(\n BluetoothSdk.uploadPhoneNotification({\n notificationId: String(event.notificationId ?? \"\"),\n packageName: String(event.packageName ?? \"\"),\n appName: String(event.app ?? \"\"),\n title: String(event.title ?? \"\"),\n subtitle: \"\",\n body: String(event.content ?? \"\"),\n timestampMs: Number(event.timestamp) || Date.now(),\n }),\n )\n}\n\nfunction pushListenerConfig(): void {\n if (Platform.OS !== \"android\") return\n const s = useSettingsStore.getState()\n const listenerEnabled = Boolean(s.getSetting(SETTINGS.android_notification_listener_enabled.key))\n const blocklist = s.getSetting(SETTINGS.notifications_blocklist.key)\n CrustModule.setNotificationConfig(listenerEnabled, Array.isArray(blocklist) ? blocklist : []).catch((err: unknown) =>\n console.warn(`PhoneNotificationsSync: setNotificationConfig failed: ${(err as Error)?.message ?? err}`),\n )\n}\n\nfunction pushGlassesConfig(): void {\n if (!nativeNotificationsSupported() && !notificationRelaySupported()) return\n if (!isGlassesConnected(useGlassesStore.getState().connection)) return\n void Promise.resolve(BluetoothSdk.setGlassesNotificationConfig(desiredGlassesNotificationConfig())).catch(\n (err: unknown) =>\n console.warn(`PhoneNotificationsSync: glasses notification config failed: ${(err as Error)?.message ?? err}`),\n )\n}\n\nfunction pushAll(): void {\n pushListenerConfig()\n pushGlassesConfig()\n}\n\n/** Record a glasses-reported source app. Writes only on new app / renamed app. */\nfunction recordSeenApp(event: NotificationAppSeenEvent): void {\n if (!event.appId) return\n const seen = getSeenNotificationApps()\n const existing = seen.find((app) => app.appId === event.appId)\n const displayName = event.displayName || existing?.displayName || event.appId\n if (existing && existing.displayName === displayName) return\n const next = seen\n .filter((app) => app.appId !== event.appId)\n .concat([{appId: event.appId, displayName, lastSeen: event.timestamp || Date.now()}])\n useSettingsStore.getState().setSetting(SETTINGS.notification_seen_apps.key, next)\n}\n\nexport function startPhoneNotificationsSync(): void {\n if (unsubscribers.length) return\n pushAll()\n const seenApps = BluetoothSdk.addListener(\"notification_app_seen\", recordSeenApp)\n unsubscribers = [\n useSettingsStore.subscribe(\n (state) =>\n [\n SETTINGS.android_notification_listener_enabled,\n SETTINGS.notifications_blocklist,\n SETTINGS.notification_seen_apps,\n SETTINGS.notifications_enabled,\n SETTINGS.notifications_popups,\n SETTINGS.notifications_popup_seconds,\n SETTINGS.notifications_hold_during_apps,\n SETTINGS.notifications_ignore_while_active,\n ].map((setting) => state.getSetting(setting.key)),\n pushAll,\n {equalityFn: shallow},\n ),\n // A (re)connected driver gets the current config; the SDK dedupes nothing\n // here, so only connection state and model are watched, not every field.\n useGlassesStore.subscribe(\n (state) => ({model: state.deviceModel, connected: isGlassesConnected(state.connection)}),\n pushGlassesConfig,\n {equalityFn: shallow},\n ),\n // Lock / foreground changes flip the effective popups flag for firmware\n // that pops notifications up by itself.\n onPhoneActivityChange(pushGlassesConfig),\n () => seenApps.remove(),\n ]\n}\n\nexport function stopPhoneNotificationsSync(): void {\n for (const unsubscribe of unsubscribers) unsubscribe()\n unsubscribers = []\n}\n"]}
@@ -77,6 +77,14 @@ declare class SceneRenderer {
77
77
  * output byte-identical to today).
78
78
  */
79
79
  emitScene(appId: string, view: "main" | "dashboard", elements: SceneElementInput[]): SceneEmitResult;
80
+ /**
81
+ * Hand native the frame `elements` would make for (app, view) without
82
+ * showing it or touching the retained scene: drivers with an on-glass asset
83
+ * cache upload its fonts and images while the panel is idle. Every element
84
+ * is "created" and the frame carries seq 0 (no presentation report). Empty
85
+ * elements drop it. False when there is nothing to send it to.
86
+ */
87
+ prefetchScene(appId: string, view: "main" | "dashboard", elements: SceneElementInput[]): boolean;
80
88
  /**
81
89
  * Re-emit an app's retained scene (reconnect / arbitration restore). Replay
82
90
  * is always create-based — the device rebuilds from scratch (spec §4).
@@ -1 +1 @@
1
- {"version":3,"file":"SceneRenderer.d.ts","sourceRoot":"","sources":["../../src/services/SceneRenderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAUH,OAAO,EAML,KAAK,YAAY,EAEjB,KAAK,wBAAwB,EAC7B,KAAK,iBAAiB,EAEvB,MAAM,wBAAwB,CAAA;AAI/B,2DAA2D;AAC3D,MAAM,MAAM,YAAY,GAAG;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAC,CAAA;AAEvE,MAAM,MAAM,eAAe;AACzB;;;;;GAKG;AACD;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAC;AACrG,0GAA0G;GACxG;IAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAC;AACrF,uDAAuD;GACrD;IAAC,IAAI,EAAE,YAAY,CAAA;CAAC,CAAA;AAExB,6FAA6F;AAC7F,MAAM,WAAW,iBAAiB;IAChC,wFAAwF;IACxF,SAAS,EAAE,OAAO,CAAA;IAClB,+CAA+C;IAC/C,UAAU,EAAE,OAAO,CAAA;IACnB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAoBD,cAAM,aAAa;IACjB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA6B;IACpD,OAAO,CAAC,KAAK,CAAmB;IAChC,OAAO,CAAC,UAAU,CAAI;IACtB,OAAO,CAAC,OAAO,CAA8E;IAC7F,OAAO,CAAC,YAAY,CAAoC;IACxD,OAAO,CAAC,UAAU,CAAoC;IACtD,OAAO,CAAC,gBAAgB,CAAmC;WAE7C,WAAW,IAAI,aAAa;IAW1C;;;OAGG;IACI,mBAAmB,IAAI,wBAAwB,GAAG,IAAI;IA6B7D;;;;OAIG;IACI,mBAAmB,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,wBAAwB,GAAG,iBAAiB,EAAE,GAAG,IAAI;IA+F5G;;;;;OAKG;IACI,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,QAAQ,EAAE,iBAAiB,EAAE,GAAG,eAAe;IA+C3G;;;;OAIG;IACI,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,OAAO;IAOpE,0FAA0F;IACnF,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IASpC;;;;OAIG;IACI,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAgB/F,OAAO,CAAC,uBAAuB;IAO/B;;;;;OAKG;IACI,SAAS,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAAG;QAAC,MAAM,EAAE,MAAM,IAAI,CAAA;KAAC;IAYvE,6FAA6F;IAC7F,OAAO,CAAC,gBAAgB;IAYxB,OAAO,CAAC,aAAa;IAWrB;;;;;;;OAOG;IACI,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAIrE;;;;;OAKG;IACI,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAMtD,2FAA2F;IAC3F,OAAO,CAAC,SAAS;IA2CV,aAAa,IAAI,IAAI;IAWrB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,YAAY,EAAE;CAGtE;AAED,QAAA,MAAM,aAAa,eAA8B,CAAA;AACjD,eAAe,aAAa,CAAA;AAC5B,OAAO,EAAC,aAAa,EAAC,CAAA"}
1
+ {"version":3,"file":"SceneRenderer.d.ts","sourceRoot":"","sources":["../../src/services/SceneRenderer.ts"],"names":[],"mappings":"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"}
@@ -200,7 +200,7 @@ class SceneRenderer {
200
200
  const { layout, degraded, dropped } = degradeScene(elements);
201
201
  return { kind: "legacy", layout, degraded, dropped };
202
202
  }
203
- const processed = processScene(elements, caps, displayProcessor.getProfile());
203
+ const processed = processScene(elements, caps, displayProcessor.getSceneProfile());
204
204
  // A stale baseline means the glasses were cleared since the last commit —
205
205
  // diff from empty so everything repaints (the retained frame stays valid
206
206
  // as the replay source only).
@@ -227,6 +227,34 @@ class SceneRenderer {
227
227
  settleMs: caps.animation ? settleTimeOf(diffed) : 0,
228
228
  };
229
229
  }
230
+ /**
231
+ * Hand native the frame `elements` would make for (app, view) without
232
+ * showing it or touching the retained scene: drivers with an on-glass asset
233
+ * cache upload its fonts and images while the panel is idle. Every element
234
+ * is "created" and the frame carries seq 0 (no presentation report). Empty
235
+ * elements drop it. False when there is nothing to send it to.
236
+ */
237
+ prefetchScene(appId, view, elements) {
238
+ const caps = this.currentCapabilities();
239
+ if (!caps?.canPosition)
240
+ return false;
241
+ const processed = processScene(resolveAnchors(elements, caps.width, caps.height), caps, displayProcessor.getSceneProfile());
242
+ let synthetic = 0;
243
+ const { elements: created } = diffScene([], processed.elements, () => `~prefetch${synthetic++}`);
244
+ const frame = {
245
+ appId,
246
+ view,
247
+ sceneEpoch: 0,
248
+ seq: 0,
249
+ elements: created,
250
+ removed: [],
251
+ canvas: { width: caps.width, height: caps.height },
252
+ };
253
+ void Promise.resolve(BluetoothSdk.displayEvent({ view, scene: frame, prefetch: true })).catch((err) => {
254
+ console.error(`${LOG_TAG}: native prefetch failed:`, err);
255
+ });
256
+ return true;
257
+ }
230
258
  /**
231
259
  * Re-emit an app's retained scene (reconnect / arbitration restore). Replay
232
260
  * is always create-based — the device rebuilds from scratch (spec §4).