@glassly/engine 0.1.0-dev.1 → 0.1.0-dev.100

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 (343) hide show
  1. package/README.md +57 -19
  2. package/build/bluetooth-sdk/debug.d.ts +2 -0
  3. package/build/bluetooth-sdk/debug.d.ts.map +1 -0
  4. package/build/bluetooth-sdk/debug.js +2 -0
  5. package/build/bluetooth-sdk/debug.js.map +1 -0
  6. package/build/bluetooth-sdk/index.d.ts +3 -0
  7. package/build/bluetooth-sdk/index.d.ts.map +1 -0
  8. package/build/bluetooth-sdk/index.js +3 -0
  9. package/build/bluetooth-sdk/index.js.map +1 -0
  10. package/build/bluetooth-sdk/ota-transport.d.ts +2 -0
  11. package/build/bluetooth-sdk/ota-transport.d.ts.map +1 -0
  12. package/build/bluetooth-sdk/ota-transport.js +2 -0
  13. package/build/bluetooth-sdk/ota-transport.js.map +1 -0
  14. package/build/bluetooth-sdk/photo-receiver.d.ts +3 -0
  15. package/build/bluetooth-sdk/photo-receiver.d.ts.map +1 -0
  16. package/build/bluetooth-sdk/photo-receiver.js +3 -0
  17. package/build/bluetooth-sdk/photo-receiver.js.map +1 -0
  18. package/build/bluetooth-sdk/react.d.ts +2 -0
  19. package/build/bluetooth-sdk/react.d.ts.map +1 -0
  20. package/build/bluetooth-sdk/react.js +2 -0
  21. package/build/bluetooth-sdk/react.js.map +1 -0
  22. package/build/bluetooth-sdk/types.d.ts +2 -0
  23. package/build/bluetooth-sdk/types.d.ts.map +1 -0
  24. package/build/bluetooth-sdk/types.js +2 -0
  25. package/build/bluetooth-sdk/types.js.map +1 -0
  26. package/build/engine.d.ts +30 -18
  27. package/build/engine.d.ts.map +1 -1
  28. package/build/engine.js +12 -3
  29. package/build/engine.js.map +1 -1
  30. package/build/facades/dev.d.ts +1 -1
  31. package/build/facades/display.d.ts +11 -4
  32. package/build/facades/display.d.ts.map +1 -1
  33. package/build/facades/display.js +12 -2
  34. package/build/facades/display.js.map +1 -1
  35. package/build/facades/displayMirror.d.ts +2 -6
  36. package/build/facades/displayMirror.d.ts.map +1 -1
  37. package/build/facades/displayMirror.js +7 -14
  38. package/build/facades/displayMirror.js.map +1 -1
  39. package/build/facades/glasses.d.ts +6 -13
  40. package/build/facades/glasses.d.ts.map +1 -1
  41. package/build/facades/glasses.js +7 -3
  42. package/build/facades/glasses.js.map +1 -1
  43. package/build/facades/ota.d.ts +21 -21
  44. package/build/facades/ota.d.ts.map +1 -1
  45. package/build/facades/ota.js +26 -10
  46. package/build/facades/ota.js.map +1 -1
  47. package/build/facades/pairing.d.ts +11 -5
  48. package/build/facades/pairing.d.ts.map +1 -1
  49. package/build/facades/pairing.js +93 -17
  50. package/build/facades/pairing.js.map +1 -1
  51. package/build/facades/phoneNotifications.d.ts +12 -2
  52. package/build/facades/phoneNotifications.d.ts.map +1 -1
  53. package/build/facades/phoneNotifications.js +30 -10
  54. package/build/facades/phoneNotifications.js.map +1 -1
  55. package/build/facades/speakers.d.ts +1 -1
  56. package/build/facades/speakers.d.ts.map +1 -1
  57. package/build/facades/speakers.js.map +1 -1
  58. package/build/facades/speech.d.ts +7 -6
  59. package/build/facades/speech.d.ts.map +1 -1
  60. package/build/facades/speech.js +1 -1
  61. package/build/facades/speech.js.map +1 -1
  62. package/build/generated/releaseMetadata.d.ts +12 -0
  63. package/build/generated/releaseMetadata.d.ts.map +1 -0
  64. package/build/generated/releaseMetadata.js +10 -0
  65. package/build/generated/releaseMetadata.js.map +1 -0
  66. package/build/index.d.ts +9 -6
  67. package/build/index.d.ts.map +1 -1
  68. package/build/index.js +7 -5
  69. package/build/index.js.map +1 -1
  70. package/build/react/MentraLiveOtaFlow.d.ts +44 -0
  71. package/build/react/MentraLiveOtaFlow.d.ts.map +1 -0
  72. package/build/react/MentraLiveOtaFlow.js +322 -0
  73. package/build/react/MentraLiveOtaFlow.js.map +1 -0
  74. package/build/react/index.d.ts +3 -0
  75. package/build/react/index.d.ts.map +1 -0
  76. package/build/react/index.js +3 -0
  77. package/build/react/index.js.map +1 -0
  78. package/build/react/useEngineSnapshot.d.ts +2 -0
  79. package/build/react/useEngineSnapshot.d.ts.map +1 -0
  80. package/build/react/useEngineSnapshot.js +14 -0
  81. package/build/react/useEngineSnapshot.js.map +1 -0
  82. package/build/react/useMentraLiveOta.d.ts +84 -0
  83. package/build/react/useMentraLiveOta.d.ts.map +1 -0
  84. package/build/react/useMentraLiveOta.js +627 -0
  85. package/build/react/useMentraLiveOta.js.map +1 -0
  86. package/build/services/AppRegistry.d.ts +8 -0
  87. package/build/services/AppRegistry.d.ts.map +1 -1
  88. package/build/services/AppRegistry.js +15 -0
  89. package/build/services/AppRegistry.js.map +1 -1
  90. package/build/services/BlobStore.d.ts +2 -0
  91. package/build/services/BlobStore.d.ts.map +1 -1
  92. package/build/services/BlobStore.js +63 -10
  93. package/build/services/BlobStore.js.map +1 -1
  94. package/build/services/CustomDashboardModel.d.ts +48 -0
  95. package/build/services/CustomDashboardModel.d.ts.map +1 -0
  96. package/build/services/CustomDashboardModel.js +73 -0
  97. package/build/services/CustomDashboardModel.js.map +1 -0
  98. package/build/services/CustomDashboardService.d.ts +55 -0
  99. package/build/services/CustomDashboardService.d.ts.map +1 -0
  100. package/build/services/CustomDashboardService.js +492 -0
  101. package/build/services/CustomDashboardService.js.map +1 -0
  102. package/build/services/DashboardCoordinator.d.ts +26 -0
  103. package/build/services/DashboardCoordinator.d.ts.map +1 -0
  104. package/build/services/DashboardCoordinator.js +125 -0
  105. package/build/services/DashboardCoordinator.js.map +1 -0
  106. package/build/services/DeviceEventRouter.d.ts.map +1 -1
  107. package/build/services/DeviceEventRouter.js +45 -6
  108. package/build/services/DeviceEventRouter.js.map +1 -1
  109. package/build/services/DisplayProcessor.d.ts +3 -3
  110. package/build/services/DisplayProcessor.js +3 -3
  111. package/build/services/DisplayProcessor.js.map +1 -1
  112. package/build/services/GestureDotsService.d.ts +40 -0
  113. package/build/services/GestureDotsService.d.ts.map +1 -0
  114. package/build/services/GestureDotsService.js +167 -0
  115. package/build/services/GestureDotsService.js.map +1 -0
  116. package/build/services/GlassesStatusProjection.d.ts +15 -1
  117. package/build/services/GlassesStatusProjection.d.ts.map +1 -1
  118. package/build/services/GlassesStatusProjection.js +18 -11
  119. package/build/services/GlassesStatusProjection.js.map +1 -1
  120. package/build/services/HotspotOtaTransport.d.ts.map +1 -1
  121. package/build/services/HotspotOtaTransport.js +8 -7
  122. package/build/services/HotspotOtaTransport.js.map +1 -1
  123. package/build/services/LocalDisplayManager.d.ts +55 -3
  124. package/build/services/LocalDisplayManager.d.ts.map +1 -1
  125. package/build/services/LocalDisplayManager.js +196 -16
  126. package/build/services/LocalDisplayManager.js.map +1 -1
  127. package/build/services/LocalMiniappRuntime.d.ts +13 -0
  128. package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
  129. package/build/services/LocalMiniappRuntime.js +32 -0
  130. package/build/services/LocalMiniappRuntime.js.map +1 -1
  131. package/build/services/MiniappExitGesture.d.ts +33 -0
  132. package/build/services/MiniappExitGesture.d.ts.map +1 -0
  133. package/build/services/MiniappExitGesture.js +54 -0
  134. package/build/services/MiniappExitGesture.js.map +1 -0
  135. package/build/services/OtaAutoChain.d.ts +41 -0
  136. package/build/services/OtaAutoChain.d.ts.map +1 -0
  137. package/build/services/OtaAutoChain.js +91 -0
  138. package/build/services/OtaAutoChain.js.map +1 -0
  139. package/build/services/OtaErrorMapping.d.ts +18 -0
  140. package/build/services/OtaErrorMapping.d.ts.map +1 -0
  141. package/build/services/OtaErrorMapping.js +63 -0
  142. package/build/services/OtaErrorMapping.js.map +1 -0
  143. package/build/services/OtaInstallCoordinator.d.ts +26 -3
  144. package/build/services/OtaInstallCoordinator.d.ts.map +1 -1
  145. package/build/services/OtaInstallCoordinator.js +38 -13
  146. package/build/services/OtaInstallCoordinator.js.map +1 -1
  147. package/build/services/OtaService.d.ts.map +1 -1
  148. package/build/services/OtaService.js +1 -1
  149. package/build/services/OtaService.js.map +1 -1
  150. package/build/services/OtaUpdateCheckService.d.ts +4 -1
  151. package/build/services/OtaUpdateCheckService.d.ts.map +1 -1
  152. package/build/services/OtaUpdateCheckService.js +28 -16
  153. package/build/services/OtaUpdateCheckService.js.map +1 -1
  154. package/build/services/PhoneNotificationsSync.d.ts +13 -0
  155. package/build/services/PhoneNotificationsSync.d.ts.map +1 -1
  156. package/build/services/PhoneNotificationsSync.js +101 -9
  157. package/build/services/PhoneNotificationsSync.js.map +1 -1
  158. package/build/services/PhonePhotoCoordinator.js +1 -1
  159. package/build/services/PhonePhotoCoordinator.js.map +1 -1
  160. package/build/services/PhoneStreamCoordinator.d.ts.map +1 -1
  161. package/build/services/PhoneStreamCoordinator.js +9 -6
  162. package/build/services/PhoneStreamCoordinator.js.map +1 -1
  163. package/build/services/SceneRenderer.d.ts.map +1 -1
  164. package/build/services/SceneRenderer.js +5 -0
  165. package/build/services/SceneRenderer.js.map +1 -1
  166. package/build/services/StreamLifecycleController.d.ts +4 -0
  167. package/build/services/StreamLifecycleController.d.ts.map +1 -1
  168. package/build/services/StreamLifecycleController.js +4 -0
  169. package/build/services/StreamLifecycleController.js.map +1 -1
  170. package/build/services/asg/localNetworkTransport.d.ts.map +1 -1
  171. package/build/services/asg/localNetworkTransport.js +13 -12
  172. package/build/services/asg/localNetworkTransport.js.map +1 -1
  173. package/build/services/effectiveCapabilities.d.ts +1 -1
  174. package/build/services/effectiveCapabilities.d.ts.map +1 -1
  175. package/build/services/effectiveCapabilities.js +26 -7
  176. package/build/services/effectiveCapabilities.js.map +1 -1
  177. package/build/services/gestureClaims.d.ts +18 -0
  178. package/build/services/gestureClaims.d.ts.map +1 -0
  179. package/build/services/gestureClaims.js +24 -0
  180. package/build/services/gestureClaims.js.map +1 -0
  181. package/build/services/gestureDotGlyphs.d.ts +8 -0
  182. package/build/services/gestureDotGlyphs.d.ts.map +1 -0
  183. package/build/services/gestureDotGlyphs.js +13 -0
  184. package/build/services/gestureDotGlyphs.js.map +1 -0
  185. package/build/services/glassesClockSync.d.ts.map +1 -1
  186. package/build/services/glassesClockSync.js +5 -1
  187. package/build/services/glassesClockSync.js.map +1 -1
  188. package/build/services/iosNotificationAppCatalog.d.ts +13 -0
  189. package/build/services/iosNotificationAppCatalog.d.ts.map +1 -0
  190. package/build/services/iosNotificationAppCatalog.js +28 -0
  191. package/build/services/iosNotificationAppCatalog.js.map +1 -0
  192. package/build/services/otaDisplayState.d.ts +1 -1
  193. package/build/services/otaDisplayState.d.ts.map +1 -1
  194. package/build/services/otaDisplayState.js.map +1 -1
  195. package/build/services/otaInstallPolicy.d.ts +5 -1
  196. package/build/services/otaInstallPolicy.d.ts.map +1 -1
  197. package/build/services/otaInstallPolicy.js +4 -0
  198. package/build/services/otaInstallPolicy.js.map +1 -1
  199. package/build/services/otaLegacyMapping.d.ts +1 -1
  200. package/build/services/otaLegacyMapping.d.ts.map +1 -1
  201. package/build/services/otaLegacyMapping.js.map +1 -1
  202. package/build/services/otaManifestPolicy.d.ts +14 -0
  203. package/build/services/otaManifestPolicy.d.ts.map +1 -0
  204. package/build/services/otaManifestPolicy.js +20 -0
  205. package/build/services/otaManifestPolicy.js.map +1 -0
  206. package/build/services/otaManifestUrl.d.ts +2 -1
  207. package/build/services/otaManifestUrl.d.ts.map +1 -1
  208. package/build/services/otaManifestUrl.js +24 -32
  209. package/build/services/otaManifestUrl.js.map +1 -1
  210. package/build/services/otaReleaseVersion.d.ts +10 -0
  211. package/build/services/otaReleaseVersion.d.ts.map +1 -0
  212. package/build/services/otaReleaseVersion.js +14 -0
  213. package/build/services/otaReleaseVersion.js.map +1 -0
  214. package/build/stores/appVisibility.d.ts +3 -0
  215. package/build/stores/appVisibility.d.ts.map +1 -0
  216. package/build/stores/appVisibility.js +3 -0
  217. package/build/stores/appVisibility.js.map +1 -0
  218. package/build/stores/apps.d.ts.map +1 -1
  219. package/build/stores/apps.js +7 -1
  220. package/build/stores/apps.js.map +1 -1
  221. package/build/stores/core.d.ts +3 -3
  222. package/build/stores/core.d.ts.map +1 -1
  223. package/build/stores/core.js.map +1 -1
  224. package/build/stores/display.d.ts +4 -4
  225. package/build/stores/display.d.ts.map +1 -1
  226. package/build/stores/display.js +12 -36
  227. package/build/stores/display.js.map +1 -1
  228. package/build/stores/glasses.d.ts +10 -4
  229. package/build/stores/glasses.d.ts.map +1 -1
  230. package/build/stores/glasses.js +2 -0
  231. package/build/stores/glasses.js.map +1 -1
  232. package/build/stores/settings.d.ts.map +1 -1
  233. package/build/stores/settings.js +140 -5
  234. package/build/stores/settings.js.map +1 -1
  235. package/build/types/applet.d.ts +3 -1
  236. package/build/types/applet.d.ts.map +1 -1
  237. package/build/types/applet.js +0 -3
  238. package/build/types/applet.js.map +1 -1
  239. package/build/types/capabilities/even-realities-g2.d.ts.map +1 -1
  240. package/build/types/capabilities/even-realities-g2.js +2 -2
  241. package/build/types/capabilities/even-realities-g2.js.map +1 -1
  242. package/build/types/enums.d.ts +1 -5
  243. package/build/types/enums.d.ts.map +1 -1
  244. package/build/types/enums.js +1 -8
  245. package/build/types/enums.js.map +1 -1
  246. package/build/types/hardware.d.ts +1 -1
  247. package/build/types/hardware.d.ts.map +1 -1
  248. package/build/types/hardware.js +1 -0
  249. package/build/types/hardware.js.map +1 -1
  250. package/build/types/index.d.ts +3 -7
  251. package/build/types/index.d.ts.map +1 -1
  252. package/build/types/index.js +3 -7
  253. package/build/types/index.js.map +1 -1
  254. package/build/utils/display/composer/ColumnComposer.d.ts +1 -1
  255. package/build/utils/display/composer/ColumnComposer.js +1 -1
  256. package/build/utils/display/composer/ColumnComposer.js.map +1 -1
  257. package/build/utils/display/scene/process.d.ts.map +1 -1
  258. package/build/utils/display/scene/process.js +4 -3
  259. package/build/utils/display/scene/process.js.map +1 -1
  260. package/build/utils/display/scene/types.d.ts +6 -2
  261. package/build/utils/display/scene/types.d.ts.map +1 -1
  262. package/build/utils/display/scene/types.js.map +1 -1
  263. package/package.json +69 -35
  264. package/src/bluetooth-sdk/debug.ts +1 -0
  265. package/src/bluetooth-sdk/index.ts +2 -0
  266. package/src/bluetooth-sdk/ota-transport.ts +1 -0
  267. package/src/bluetooth-sdk/photo-receiver.ts +2 -0
  268. package/src/bluetooth-sdk/react.ts +1 -0
  269. package/src/bluetooth-sdk/types.ts +1 -0
  270. package/src/engine.ts +12 -3
  271. package/src/facades/display.ts +12 -2
  272. package/src/facades/displayMirror.ts +7 -15
  273. package/src/facades/glasses.ts +8 -4
  274. package/src/facades/ota.ts +36 -11
  275. package/src/facades/pairing.ts +103 -23
  276. package/src/facades/phoneNotifications.ts +32 -10
  277. package/src/facades/speakers.ts +1 -1
  278. package/src/facades/speech.ts +21 -7
  279. package/src/generated/releaseMetadata.ts +20 -0
  280. package/src/index.ts +31 -5
  281. package/src/react/MentraLiveOtaFlow.tsx +754 -0
  282. package/src/react/index.ts +21 -0
  283. package/src/react/useEngineSnapshot.ts +16 -0
  284. package/src/react/useMentraLiveOta.ts +793 -0
  285. package/src/services/AppRegistry.ts +16 -0
  286. package/src/services/BlobStore.ts +57 -10
  287. package/src/services/CustomDashboardModel.ts +83 -0
  288. package/src/services/CustomDashboardService.ts +516 -0
  289. package/src/services/DashboardCoordinator.ts +121 -0
  290. package/src/services/DeviceEventRouter.ts +48 -8
  291. package/src/services/DisplayProcessor.ts +3 -3
  292. package/src/services/GestureDotsService.ts +164 -0
  293. package/src/services/GlassesStatusProjection.ts +20 -11
  294. package/src/services/HotspotOtaTransport.ts +8 -7
  295. package/src/services/LocalDisplayManager.ts +198 -18
  296. package/src/services/LocalMiniappRuntime.ts +37 -0
  297. package/src/services/MiniappExitGesture.ts +76 -0
  298. package/src/services/OtaAutoChain.ts +130 -0
  299. package/src/services/OtaErrorMapping.ts +80 -0
  300. package/src/services/OtaInstallCoordinator.ts +40 -13
  301. package/src/services/OtaService.ts +1 -2
  302. package/src/services/OtaUpdateCheckService.ts +33 -19
  303. package/src/services/PhoneNotificationsSync.ts +125 -9
  304. package/src/services/PhonePhotoCoordinator.ts +1 -1
  305. package/src/services/PhoneStreamCoordinator.ts +10 -6
  306. package/src/services/SceneRenderer.ts +4 -0
  307. package/src/services/StreamLifecycleController.ts +4 -0
  308. package/src/services/asg/localNetworkTransport.ts +20 -19
  309. package/src/services/effectiveCapabilities.ts +27 -7
  310. package/src/services/gestureClaims.ts +28 -0
  311. package/src/services/gestureDotGlyphs.ts +15 -0
  312. package/src/services/glassesClockSync.ts +5 -1
  313. package/src/services/iosNotificationAppCatalog.ts +39 -0
  314. package/src/services/otaDisplayState.ts +1 -1
  315. package/src/services/otaInstallPolicy.ts +5 -1
  316. package/src/services/otaLegacyMapping.ts +1 -1
  317. package/src/services/otaManifestPolicy.ts +43 -0
  318. package/src/services/otaManifestUrl.ts +27 -36
  319. package/src/services/otaReleaseVersion.ts +22 -0
  320. package/src/stores/appVisibility.ts +3 -0
  321. package/src/stores/apps.ts +7 -1
  322. package/src/stores/core.ts +4 -4
  323. package/src/stores/display.ts +16 -48
  324. package/src/stores/glasses.ts +15 -6
  325. package/src/stores/settings.ts +140 -5
  326. package/src/types/applet.ts +3 -1
  327. package/src/types/capabilities/even-realities-g2.ts +3 -2
  328. package/src/types/enums.ts +1 -9
  329. package/src/types/hardware.ts +2 -1
  330. package/src/types/index.ts +3 -7
  331. package/src/utils/display/composer/ColumnComposer.ts +1 -1
  332. package/src/utils/display/scene/process.ts +4 -3
  333. package/src/utils/display/scene/types.ts +0 -0
  334. package/build/devtools.d.ts +0 -11
  335. package/build/devtools.d.ts.map +0 -1
  336. package/build/devtools.js +0 -13
  337. package/build/devtools.js.map +0 -1
  338. package/build/internal.d.ts +0 -67
  339. package/build/internal.d.ts.map +0 -1
  340. package/build/internal.js +0 -90
  341. package/build/internal.js.map +0 -1
  342. package/src/devtools.ts +0 -13
  343. package/src/internal.ts +0 -155
@@ -12,11 +12,13 @@
12
12
  import BluetoothSdk from "@glassly/bluetooth-sdk/internal";
13
13
  import { useCoreStore } from "../stores/core";
14
14
  import { useGlassesStore } from "../stores/glasses";
15
+ import { SETTINGS, useSettingsStore } from "../stores/settings";
15
16
  import { hasDefaultDevice } from "../services/DeviceStoreHydration";
16
17
  import { markPendingSelection, projectPairingIdentity, subscribePairingIdentity, } from "../services/PairingIdentity";
17
- import { isGlassesConnected, isGlassesLinkLayerBusy, isGlassesReady, waitForGlassesReady, } from "../services/GlassesReadiness";
18
+ import { isGlassesConnected, isGlassesLinkLayerBusy, isGlassesReady } from "../services/GlassesReadiness";
18
19
  import { logAutomaticReportSubmissionStatus, toAutomaticReportSubmissionStatus, } from "../services/AutomaticReportResult";
19
20
  import { pushAllBluetoothSettings } from "../services/GlassesSettingsSync";
21
+ import { ControllerTypes } from "../types";
20
22
  import { submitAutomaticReport } from "./reports";
21
23
  const DEFAULT_PAIRING_BOOT_TIMEOUT_MS = 35_000;
22
24
  const DEFAULT_BT_CLASSIC_TIMEOUT_MS = 1_000;
@@ -32,6 +34,79 @@ function projectReadiness() {
32
34
  nativeLinkBusy: isGlassesLinkLayerBusy(s.connection),
33
35
  };
34
36
  }
37
+ function projectTargetReady(options) {
38
+ const { deviceModel, deviceName } = options;
39
+ const glasses = useGlassesStore.getState();
40
+ if (deviceModel === ControllerTypes.R1) {
41
+ if (!glasses.controllerConnected || !glasses.controllerFullyBooted)
42
+ return false;
43
+ const settings = useSettingsStore.getState();
44
+ const pairedModel = settings.getSetting(SETTINGS.default_controller.key);
45
+ const pairedName = settings.getSetting(SETTINGS.controller_device_name.key);
46
+ return (!deviceModel || pairedModel === deviceModel) && (!deviceName || pairedName === deviceName);
47
+ }
48
+ if (!isGlassesReady(glasses.connection))
49
+ return false;
50
+ const identity = projectPairingIdentity();
51
+ if (!deviceModel && !deviceName)
52
+ return true;
53
+ return (identity.kind === "paired" &&
54
+ (!deviceModel || identity.model === deviceModel) &&
55
+ (!deviceName || identity.name === deviceName));
56
+ }
57
+ function subscribeTargetReady(options, cb) {
58
+ let last = projectTargetReady(options);
59
+ const notify = () => {
60
+ const ready = projectTargetReady(options);
61
+ if (ready === last)
62
+ return;
63
+ last = ready;
64
+ cb(ready);
65
+ };
66
+ const unsubscribeGlasses = useGlassesStore.subscribe(notify);
67
+ const unsubscribeSettings = useSettingsStore.subscribe(notify);
68
+ return () => {
69
+ unsubscribeGlasses();
70
+ unsubscribeSettings();
71
+ };
72
+ }
73
+ async function waitForTargetReady(options, timeoutMs) {
74
+ return new Promise((resolve) => {
75
+ if (options.signal?.aborted) {
76
+ resolve(false);
77
+ return;
78
+ }
79
+ if (projectTargetReady(options)) {
80
+ resolve(true);
81
+ return;
82
+ }
83
+ let settled = false;
84
+ let timeout = null;
85
+ let unsubscribe = null;
86
+ let onAbort = null;
87
+ const finish = (ready) => {
88
+ if (settled)
89
+ return;
90
+ settled = true;
91
+ if (timeout)
92
+ clearTimeout(timeout);
93
+ if (unsubscribe)
94
+ unsubscribe();
95
+ if (onAbort && options.signal)
96
+ options.signal.removeEventListener("abort", onAbort);
97
+ resolve(ready);
98
+ };
99
+ unsubscribe = subscribeTargetReady(options, (ready) => {
100
+ if (ready)
101
+ finish(true);
102
+ });
103
+ if (options.signal) {
104
+ onAbort = () => finish(false);
105
+ options.signal.addEventListener("abort", onAbort);
106
+ }
107
+ timeout = setTimeout(() => finish(projectTargetReady(options)), timeoutMs);
108
+ });
109
+ }
35
110
  export async function submitPairingBootTimeoutReport(params) {
36
111
  const { deviceModel, deviceName, showGlassesBooting, elapsedMs, route = DEFAULT_PAIRING_ROUTE } = params;
37
112
  const result = await submitAutomaticReport({
@@ -65,12 +140,7 @@ async function waitForReadyDuringPairing(options = {}) {
65
140
  showGlassesBooting = true;
66
141
  });
67
142
  try {
68
- const ready = await waitForGlassesReady({
69
- getConnection: () => useGlassesStore.getState().connection,
70
- subscribe: (listener) => useGlassesStore.subscribe((state) => state.connection, listener),
71
- timeoutMs,
72
- signal: options.signal,
73
- });
143
+ const ready = await waitForTargetReady(options, timeoutMs);
74
144
  if (!ready && !options.signal?.aborted) {
75
145
  void submitPairingBootTimeoutReport({
76
146
  deviceModel: options.deviceModel,
@@ -142,6 +212,10 @@ export const pairing = {
142
212
  cb(snap);
143
213
  });
144
214
  },
215
+ /** Whether the exact wearable or controller selected on the scan screen is ready. */
216
+ targetReady: (options) => projectTargetReady(options),
217
+ /** Subscribe to readiness for the exact selected target, excluding stale devices. */
218
+ onTargetReady: (options, cb) => subscribeTargetReady(options, cb),
145
219
  // --- pairing-identity lifecycle (the PairingIdentity read-model + the JS-owned
146
220
  // identity writes; promotion to `paired` only ever happens natively) ---
147
221
  /** The identity lifecycle snapshot: none | pending (chosen, never paired) | paired. */
@@ -153,7 +227,7 @@ export const pairing = {
153
227
  * host renders it as a finish-pairing affordance until pairing succeeds. */
154
228
  markPendingSelection: (model) => markPendingSelection(model),
155
229
  /** Start scanning for nearby glasses. Results land on `searchResults()`/`onFound()`. */
156
- scan: (...args) => BluetoothSdk.startScan(...args),
230
+ scan: (model) => BluetoothSdk.startScan(model),
157
231
  /** Whether a scan is currently in progress. */
158
232
  scanning: () => useCoreStore.getState().searching,
159
233
  /** Subscribe to scan-in-progress changes; fires only when it changes. Returns an unsubscribe. */
@@ -195,7 +269,7 @@ export const pairing = {
195
269
  return BluetoothSdk.connect(device, { ...options, saveAsDefault: false });
196
270
  },
197
271
  /** Set a device as the default for subsequent `glasses.connectDefault()`. */
198
- setDefault: (...args) => BluetoothSdk.setDefaultDevice(...args),
272
+ setDefault: (device) => BluetoothSdk.setDefaultDevice(device),
199
273
  /**
200
274
  * Prime the native Bluetooth Classic audio watcher with the picked device.
201
275
  * The iOS Mentra Live flow pairs Classic audio BEFORE any BLE connect
@@ -235,27 +309,29 @@ export const pairing = {
235
309
  await BluetoothSdk.stopScan();
236
310
  return;
237
311
  }
238
- await BluetoothSdk.disconnect();
239
312
  if (projectPairingIdentity().kind === "paired") {
240
313
  // The persisted settings describe a COMPLETE pairing: restore it to
241
314
  // native (the attempt's connect-by-name overwrote the native
242
315
  // device_name; and if native somehow lost its default entirely, this
243
316
  // repairs the divergence instead of forgetting a real pairing).
244
317
  console.log("PairingIdentity: abandonAttempt — preserving pairing; attempt cancelled, native identity re-seeded");
318
+ await BluetoothSdk.disconnect();
245
319
  await pushAllBluetoothSettings();
320
+ return;
246
321
  }
247
- else if (nativeHasDefault) {
322
+ if (nativeHasDefault) {
248
323
  // Mid-relay: native promoted and its echoes are still landing — the
249
324
  // incomplete JS snapshot must not be pushed over the fresher native
250
325
  // identity (the on-connect replay's race). Native holds the truth.
251
326
  console.log("PairingIdentity: abandonAttempt — preserving pairing; JS identity mid-relay, native kept as-is");
327
+ await BluetoothSdk.disconnect();
328
+ return;
252
329
  }
253
- else {
254
- // Forgetting requires CONSENSUS: no native default AND no complete
255
- // persisted pairing — a genuinely partial attempt.
256
- console.log("PairingIdentity: abandonAttempt — no pairing on either layer; forgetting the partial attempt");
257
- await BluetoothSdk.forget();
258
- }
330
+ // Partial attempt: forget owns teardown. Do NOT disconnect() first — that
331
+ // nulls the MentraLive SGC and used to skip Classic removeBond, leaving
332
+ // IBRT ACL up so glasses never re-advertise for the next scan.
333
+ console.log("PairingIdentity: abandonAttempt — no pairing on either layer; forgetting the partial attempt");
334
+ await BluetoothSdk.forget();
259
335
  },
260
336
  /** Subscribe to pairing failures; returns an unsubscribe. */
261
337
  onPairFailure: (cb) => {
@@ -1 +1 @@
1
- {"version":3,"file":"pairing.js","sourceRoot":"","sources":["../../src/facades/pairing.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * pairing facade — `engine.pairing`: scan for nearby glasses + pair. Scan state\n * (searching + results) comes from the engine core store; scan/connect go over the\n * bluetooth-sdk; pair-failure / not-ready are the bluetooth-sdk events.\n *\n * (Connect-the-already-paired-default is `engine.glasses.connectDefault()`; this\n * facade is the first-time discovery + pair flow.)\n */\n// Internal btsdk surface — updateBluetoothSettings (the Bluetooth Classic\n// target hint) lives on the full surface, not the public entry (same reason\n// the glasses facade imports internal).\nimport BluetoothSdk from \"@glassly/bluetooth-sdk/internal\"\nimport type {ConnectOptions, Device, PairFailureEvent, GlassesNotReadyEvent} from \"@glassly/bluetooth-sdk\"\nimport {useCoreStore} from \"../stores/core\"\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {hasDefaultDevice} from \"../services/DeviceStoreHydration\"\nimport {\n markPendingSelection,\n projectPairingIdentity,\n subscribePairingIdentity,\n type IdentitySnapshot,\n} from \"../services/PairingIdentity\"\nimport {\n isGlassesConnected,\n isGlassesLinkLayerBusy,\n isGlassesReady,\n waitForGlassesReady,\n} from \"../services/GlassesReadiness\"\nimport {\n logAutomaticReportSubmissionStatus,\n toAutomaticReportSubmissionStatus,\n type AutomaticReportSubmissionStatus,\n} from \"../services/AutomaticReportResult\"\nimport {pushAllBluetoothSettings} from \"../services/GlassesSettingsSync\"\nimport {submitAutomaticReport} from \"./reports\"\n\nexport type {IdentitySnapshot} from \"../services/PairingIdentity\"\n\nexport interface PairingReadyWaitOptions {\n deviceModel?: string\n deviceName?: string\n timeoutMs?: number\n route?: string\n signal?: AbortSignal\n}\n\nconst DEFAULT_PAIRING_BOOT_TIMEOUT_MS = 35_000\nconst DEFAULT_BT_CLASSIC_TIMEOUT_MS = 1_000\nconst DEFAULT_PAIRING_ROUTE = \"/pairing/loading\"\nconst LOG_TAG = \"PairingTimeoutReport\"\n\nfunction projectReadiness() {\n const s = useGlassesStore.getState()\n return {\n state: s.connection.state,\n connected: s.connection.state === \"connected\",\n fullyBooted: isGlassesReady(s.connection),\n bluetoothClassicConnected: s.bluetoothClassicConnected,\n nativeLinkBusy: isGlassesLinkLayerBusy(s.connection),\n }\n}\n\nexport type PairingReadinessSnapshot = ReturnType<typeof projectReadiness>\n\nexport async function submitPairingBootTimeoutReport(params: {\n deviceModel?: string\n deviceName?: string\n showGlassesBooting: boolean\n elapsedMs: number\n route?: string\n}): Promise<AutomaticReportSubmissionStatus> {\n const {deviceModel, deviceName, showGlassesBooting, elapsedMs, route = DEFAULT_PAIRING_ROUTE} = params\n const result = await submitAutomaticReport({\n kind: \"automatic\",\n trigger: {\n type: \"automatic\",\n source: \"pairing_loading\",\n reason: \"glasses_connect_timeout\",\n },\n report: {\n expectedBehavior: `Glasses should connect successfully within ${Math.round(elapsedMs / 1000)} seconds.`,\n actualBehavior: JSON.stringify(\n {\n deviceModel,\n deviceName,\n showGlassesBooting,\n elapsedMs,\n route,\n },\n null,\n 2,\n ),\n systemPriority: \"medium\",\n },\n throttleKey: `pairing_timeout|${deviceModel || \"unknown\"}|${deviceName || \"unknown\"}`,\n })\n\n const status = toAutomaticReportSubmissionStatus(result)\n logAutomaticReportSubmissionStatus(LOG_TAG, status, deviceModel, deviceName)\n return status\n}\n\nasync function waitForReadyDuringPairing(options: PairingReadyWaitOptions = {}): Promise<boolean> {\n const timeoutMs = options.timeoutMs ?? DEFAULT_PAIRING_BOOT_TIMEOUT_MS\n let showGlassesBooting = false\n const notReadySub = BluetoothSdk.addListener(\"glasses_not_ready\", () => {\n showGlassesBooting = true\n })\n\n try {\n const ready = await waitForGlassesReady({\n getConnection: () => useGlassesStore.getState().connection,\n subscribe: (listener) => useGlassesStore.subscribe((state) => state.connection, listener),\n timeoutMs,\n signal: options.signal,\n })\n\n if (!ready && !options.signal?.aborted) {\n void submitPairingBootTimeoutReport({\n deviceModel: options.deviceModel,\n deviceName: options.deviceName,\n showGlassesBooting,\n elapsedMs: timeoutMs,\n route: options.route,\n }).catch((error) => {\n console.error(`[${LOG_TAG}] Unexpected error:`, error)\n })\n }\n\n return ready\n } finally {\n notReadySub.remove()\n }\n}\n\nasync function waitForBluetoothClassic(\n options: Pick<PairingReadyWaitOptions, \"timeoutMs\" | \"signal\"> = {},\n): Promise<boolean> {\n const timeoutMs = options.timeoutMs ?? DEFAULT_BT_CLASSIC_TIMEOUT_MS\n return new Promise((resolve) => {\n if (options.signal?.aborted) {\n resolve(false)\n return\n }\n\n const initial = useGlassesStore.getState().bluetoothClassicConnected\n if (initial) {\n resolve(true)\n return\n }\n\n let settled = false\n let timeout: ReturnType<typeof setTimeout> | null = null\n let unsubscribe: (() => void) | null = null\n let onAbort: (() => void) | null = null\n\n const finish = (value: boolean) => {\n if (settled) return\n settled = true\n if (timeout) clearTimeout(timeout)\n if (unsubscribe) unsubscribe()\n if (onAbort && options.signal) options.signal.removeEventListener(\"abort\", onAbort)\n resolve(value)\n }\n\n if (options.signal) {\n onAbort = () => finish(false)\n options.signal.addEventListener(\"abort\", onAbort)\n }\n\n unsubscribe = useGlassesStore.subscribe(\n (s) => s.bluetoothClassicConnected,\n (connected) => {\n if (connected) finish(true)\n },\n )\n\n timeout = setTimeout(() => {\n finish(useGlassesStore.getState().bluetoothClassicConnected)\n }, timeoutMs)\n })\n}\n\nexport const pairing = {\n readiness: (): PairingReadinessSnapshot => projectReadiness(),\n onReadiness: (cb: (readiness: PairingReadinessSnapshot) => void): (() => void) => {\n let last = JSON.stringify(projectReadiness())\n return useGlassesStore.subscribe(() => {\n const snap = projectReadiness()\n const key = JSON.stringify(snap)\n if (key === last) return\n last = key\n cb(snap)\n })\n },\n\n // --- pairing-identity lifecycle (the PairingIdentity read-model + the JS-owned\n // identity writes; promotion to `paired` only ever happens natively) ---\n /** The identity lifecycle snapshot: none | pending (chosen, never paired) | paired. */\n identity: (): IdentitySnapshot => projectPairingIdentity(),\n /** Subscribe to identity changes; fires only when the projected snapshot changes.\n * Returns an unsubscribe. */\n onIdentity: (cb: (identity: IdentitySnapshot) => void): (() => void) => subscribePairingIdentity(cb),\n /** Mark the chosen model as the pending selection (the scan-entry write); the\n * host renders it as a finish-pairing affordance until pairing succeeds. */\n markPendingSelection: (model: string) => markPendingSelection(model),\n\n /** Start scanning for nearby glasses. Results land on `searchResults()`/`onFound()`. */\n scan: (...args: Parameters<typeof BluetoothSdk.startScan>) => BluetoothSdk.startScan(...args),\n /** Whether a scan is currently in progress. */\n scanning: (): boolean => useCoreStore.getState().searching,\n /** Subscribe to scan-in-progress changes; fires only when it changes. Returns an unsubscribe. */\n onScanning: (cb: (scanning: boolean) => void): (() => void) => useCoreStore.subscribe((s) => s.searching, cb),\n /** Whether a controller scan is currently in progress. */\n scanningController: (): boolean => useCoreStore.getState().searchingController,\n /** Subscribe to controller-scan-in-progress changes; fires only when it changes. Returns an unsubscribe. */\n onScanningController: (cb: (scanning: boolean) => void): (() => void) =>\n useCoreStore.subscribe((s) => s.searchingController, cb),\n /** Whether the phone has another Bluetooth (audio) device connected — the BT Classic pairing hint. */\n otherBtConnected: (): boolean => useCoreStore.getState().otherBtConnected,\n /** Subscribe to other-Bluetooth-device-connected changes; fires only when it changes. Returns an unsubscribe. */\n onOtherBtConnected: (cb: (connected: boolean) => void): (() => void) =>\n useCoreStore.subscribe((s) => s.otherBtConnected, cb),\n /** The current scan results (snapshot). */\n // Copy the array AND each entry: neither the list nor the Device objects may\n // leak as mutable references into the store.\n searchResults: () => useCoreStore.getState().searchResults.map((result) => ({...result})),\n /** Subscribe to scan-result changes; fires only when they change. Returns an unsubscribe. */\n onFound: (cb: (results: ReturnType<typeof useCoreStore.getState>[\"searchResults\"]) => void): (() => void) => {\n let last = JSON.stringify(useCoreStore.getState().searchResults)\n return useCoreStore.subscribe(() => {\n const results = useCoreStore.getState().searchResults\n const key = JSON.stringify(results)\n if (key === last) return\n last = key\n // Copy like searchResults(): listeners must not mutate shared scan state.\n cb(results.map((result) => ({...result})))\n })\n },\n\n /**\n * Pair with (connect to) a discovered device. Two-phase identity: the connect\n * attempt only marks the device as pending (`saveAsDefault: false` — no eager\n * default-device write); the native layer promotes it to the default wearable\n * when pairing actually succeeds (handleDeviceReady), so an abandoned or\n * failed attempt can't leave a default identity with no paired device behind.\n */\n pair: async (device: Device, options?: ConnectOptions): Promise<void> => {\n await pushAllBluetoothSettings()\n return BluetoothSdk.connect(device, {...options, saveAsDefault: false})\n },\n /** Set a device as the default for subsequent `glasses.connectDefault()`. */\n setDefault: (...args: Parameters<typeof BluetoothSdk.setDefaultDevice>) => BluetoothSdk.setDefaultDevice(...args),\n /**\n * Prime the native Bluetooth Classic audio watcher with the picked device.\n * The iOS Mentra Live flow pairs Classic audio BEFORE any BLE connect\n * exists, and native detects the pairing by matching the connected audio\n * route against its device_name — which two-phase identity no longer sets\n * at selection time. This is native-only routing state (device_name has no\n * native→JS echo): the phone's persisted identity is untouched, and\n * promotion still happens only at pairing success.\n */\n setBluetoothClassicTarget: (device: Device): Promise<void> =>\n BluetoothSdk.updateBluetoothSettings({device_name: device.name}),\n /**\n * Abandon an in-flight pairing attempt (back-out, failure retry, conflict\n * retry) without destroying an existing pairing. The decision comes from the\n * LIVE hydrated default-device read — never from flow-entry state, because a\n * pairing can PROMOTE while the flow is open (the glasses finish pairing\n * even as the user backs out of the UI), and an entry snapshot would forget\n * that brand-new pairing:\n * - Default device exists and glasses are connected (nothing in flight — an\n * attempt drops the link first): stop the scan, touch nothing else.\n * - Default device exists with an attempt in flight: cancel it, then re-seed\n * the native identity from the phone's persisted settings — the attempt's\n * connect-by-name overwrote the native device_name, so a later\n * connectDefault() would otherwise target the abandoned device.\n * - No default device (genuinely unpaired attempt): also forget, clearing\n * the partial native pairing state.\n * The read fails OPEN to \"preserve\" — a transient failure must never wipe a\n * real pairing. The `pending_wearable` marker is deliberately left alone —\n * the host renders it as a finish-pairing affordance.\n */\n abandonAttempt: async (): Promise<void> => {\n const nativeHasDefault = await hasDefaultDevice().catch(() => true)\n if (nativeHasDefault && isGlassesConnected(useGlassesStore.getState().connection)) {\n // Still connected means no connect attempt is in flight (an attempt drops\n // the existing link first): the user browsed the scan and backed out.\n // Stop the scan and leave the live pairing untouched.\n console.log(\"PairingIdentity: abandonAttempt — pairing intact and connected; stopping scan only\")\n await BluetoothSdk.stopScan()\n return\n }\n await BluetoothSdk.disconnect()\n if (projectPairingIdentity().kind === \"paired\") {\n // The persisted settings describe a COMPLETE pairing: restore it to\n // native (the attempt's connect-by-name overwrote the native\n // device_name; and if native somehow lost its default entirely, this\n // repairs the divergence instead of forgetting a real pairing).\n console.log(\"PairingIdentity: abandonAttempt — preserving pairing; attempt cancelled, native identity re-seeded\")\n await pushAllBluetoothSettings()\n } else if (nativeHasDefault) {\n // Mid-relay: native promoted and its echoes are still landing — the\n // incomplete JS snapshot must not be pushed over the fresher native\n // identity (the on-connect replay's race). Native holds the truth.\n console.log(\"PairingIdentity: abandonAttempt — preserving pairing; JS identity mid-relay, native kept as-is\")\n } else {\n // Forgetting requires CONSENSUS: no native default AND no complete\n // persisted pairing — a genuinely partial attempt.\n console.log(\"PairingIdentity: abandonAttempt — no pairing on either layer; forgetting the partial attempt\")\n await BluetoothSdk.forget()\n }\n },\n\n /** Subscribe to pairing failures; returns an unsubscribe. */\n onPairFailure: (cb: (event: PairFailureEvent) => void): (() => void) => {\n const sub = BluetoothSdk.addListener(\"pair_failure\", cb)\n return () => sub.remove()\n },\n /** Subscribe to glasses-not-ready events during pairing; returns an unsubscribe. */\n onGlassesNotReady: (cb: (event: GlassesNotReadyEvent) => void): (() => void) => {\n const sub = BluetoothSdk.addListener(\"glasses_not_ready\", cb)\n return () => sub.remove()\n },\n /** Wait for paired glasses to finish booting; engine files the timeout diagnostic. */\n waitForReady: waitForReadyDuringPairing,\n /** Wait briefly for Bluetooth Classic to come up after BLE pairing. */\n waitForBluetoothClassic,\n}\n"]}
1
+ {"version":3,"file":"pairing.js","sourceRoot":"","sources":["../../src/facades/pairing.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * pairing facade — `engine.pairing`: scan for nearby glasses + pair. Scan state\n * (searching + results) comes from the engine core store; scan/connect go over the\n * bluetooth-sdk; pair-failure / not-ready are the bluetooth-sdk events.\n *\n * (Connect-the-already-paired-default is `engine.glasses.connectDefault()`; this\n * facade is the first-time discovery + pair flow.)\n */\n// Internal btsdk surface — updateBluetoothSettings (the Bluetooth Classic\n// target hint) lives on the full surface, not the public entry (same reason\n// the glasses facade imports internal).\nimport BluetoothSdk from \"@glassly/bluetooth-sdk/internal\"\nimport type {ConnectOptions, Device, DeviceModel, PairFailureEvent, GlassesNotReadyEvent} from \"@glassly/bluetooth-sdk\"\nimport {useCoreStore} from \"../stores/core\"\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {SETTINGS, useSettingsStore} from \"../stores/settings\"\nimport {hasDefaultDevice} from \"../services/DeviceStoreHydration\"\nimport {\n markPendingSelection,\n projectPairingIdentity,\n subscribePairingIdentity,\n type IdentitySnapshot,\n} from \"../services/PairingIdentity\"\nimport {isGlassesConnected, isGlassesLinkLayerBusy, isGlassesReady} from \"../services/GlassesReadiness\"\nimport {\n logAutomaticReportSubmissionStatus,\n toAutomaticReportSubmissionStatus,\n type AutomaticReportSubmissionStatus,\n} from \"../services/AutomaticReportResult\"\nimport {pushAllBluetoothSettings} from \"../services/GlassesSettingsSync\"\nimport {ControllerTypes} from \"../types\"\nimport {submitAutomaticReport} from \"./reports\"\n\nexport type {IdentitySnapshot} from \"../services/PairingIdentity\"\n\nexport interface PairingReadyWaitOptions {\n deviceModel?: string\n deviceName?: string\n timeoutMs?: number\n route?: string\n signal?: AbortSignal\n}\n\nconst DEFAULT_PAIRING_BOOT_TIMEOUT_MS = 35_000\nconst DEFAULT_BT_CLASSIC_TIMEOUT_MS = 1_000\nconst DEFAULT_PAIRING_ROUTE = \"/pairing/loading\"\nconst LOG_TAG = \"PairingTimeoutReport\"\n\nfunction projectReadiness() {\n const s = useGlassesStore.getState()\n return {\n state: s.connection.state,\n connected: s.connection.state === \"connected\",\n fullyBooted: isGlassesReady(s.connection),\n bluetoothClassicConnected: s.bluetoothClassicConnected,\n nativeLinkBusy: isGlassesLinkLayerBusy(s.connection),\n }\n}\n\nfunction projectTargetReady(options: Pick<PairingReadyWaitOptions, \"deviceModel\" | \"deviceName\">): boolean {\n const {deviceModel, deviceName} = options\n const glasses = useGlassesStore.getState()\n\n if (deviceModel === ControllerTypes.R1) {\n if (!glasses.controllerConnected || !glasses.controllerFullyBooted) return false\n const settings = useSettingsStore.getState()\n const pairedModel = settings.getSetting(SETTINGS.default_controller.key)\n const pairedName = settings.getSetting(SETTINGS.controller_device_name.key)\n return (!deviceModel || pairedModel === deviceModel) && (!deviceName || pairedName === deviceName)\n }\n\n if (!isGlassesReady(glasses.connection)) return false\n const identity = projectPairingIdentity()\n if (!deviceModel && !deviceName) return true\n return (\n identity.kind === \"paired\" &&\n (!deviceModel || identity.model === deviceModel) &&\n (!deviceName || identity.name === deviceName)\n )\n}\n\nfunction subscribeTargetReady(\n options: Pick<PairingReadyWaitOptions, \"deviceModel\" | \"deviceName\">,\n cb: (ready: boolean) => void,\n): () => void {\n let last = projectTargetReady(options)\n const notify = () => {\n const ready = projectTargetReady(options)\n if (ready === last) return\n last = ready\n cb(ready)\n }\n const unsubscribeGlasses = useGlassesStore.subscribe(notify)\n const unsubscribeSettings = useSettingsStore.subscribe(notify)\n return () => {\n unsubscribeGlasses()\n unsubscribeSettings()\n }\n}\n\nasync function waitForTargetReady(options: PairingReadyWaitOptions, timeoutMs: number): Promise<boolean> {\n return new Promise((resolve) => {\n if (options.signal?.aborted) {\n resolve(false)\n return\n }\n if (projectTargetReady(options)) {\n resolve(true)\n return\n }\n\n let settled = false\n let timeout: ReturnType<typeof setTimeout> | null = null\n let unsubscribe: (() => void) | null = null\n let onAbort: (() => void) | null = null\n const finish = (ready: boolean) => {\n if (settled) return\n settled = true\n if (timeout) clearTimeout(timeout)\n if (unsubscribe) unsubscribe()\n if (onAbort && options.signal) options.signal.removeEventListener(\"abort\", onAbort)\n resolve(ready)\n }\n\n unsubscribe = subscribeTargetReady(options, (ready) => {\n if (ready) finish(true)\n })\n if (options.signal) {\n onAbort = () => finish(false)\n options.signal.addEventListener(\"abort\", onAbort)\n }\n timeout = setTimeout(() => finish(projectTargetReady(options)), timeoutMs)\n })\n}\n\nexport type PairingReadinessSnapshot = ReturnType<typeof projectReadiness>\n\nexport async function submitPairingBootTimeoutReport(params: {\n deviceModel?: string\n deviceName?: string\n showGlassesBooting: boolean\n elapsedMs: number\n route?: string\n}): Promise<AutomaticReportSubmissionStatus> {\n const {deviceModel, deviceName, showGlassesBooting, elapsedMs, route = DEFAULT_PAIRING_ROUTE} = params\n const result = await submitAutomaticReport({\n kind: \"automatic\",\n trigger: {\n type: \"automatic\",\n source: \"pairing_loading\",\n reason: \"glasses_connect_timeout\",\n },\n report: {\n expectedBehavior: `Glasses should connect successfully within ${Math.round(elapsedMs / 1000)} seconds.`,\n actualBehavior: JSON.stringify(\n {\n deviceModel,\n deviceName,\n showGlassesBooting,\n elapsedMs,\n route,\n },\n null,\n 2,\n ),\n systemPriority: \"medium\",\n },\n throttleKey: `pairing_timeout|${deviceModel || \"unknown\"}|${deviceName || \"unknown\"}`,\n })\n\n const status = toAutomaticReportSubmissionStatus(result)\n logAutomaticReportSubmissionStatus(LOG_TAG, status, deviceModel, deviceName)\n return status\n}\n\nasync function waitForReadyDuringPairing(options: PairingReadyWaitOptions = {}): Promise<boolean> {\n const timeoutMs = options.timeoutMs ?? DEFAULT_PAIRING_BOOT_TIMEOUT_MS\n let showGlassesBooting = false\n const notReadySub = BluetoothSdk.addListener(\"glasses_not_ready\", () => {\n showGlassesBooting = true\n })\n\n try {\n const ready = await waitForTargetReady(options, timeoutMs)\n\n if (!ready && !options.signal?.aborted) {\n void submitPairingBootTimeoutReport({\n deviceModel: options.deviceModel,\n deviceName: options.deviceName,\n showGlassesBooting,\n elapsedMs: timeoutMs,\n route: options.route,\n }).catch((error) => {\n console.error(`[${LOG_TAG}] Unexpected error:`, error)\n })\n }\n\n return ready\n } finally {\n notReadySub.remove()\n }\n}\n\nasync function waitForBluetoothClassic(\n options: Pick<PairingReadyWaitOptions, \"timeoutMs\" | \"signal\"> = {},\n): Promise<boolean> {\n const timeoutMs = options.timeoutMs ?? DEFAULT_BT_CLASSIC_TIMEOUT_MS\n return new Promise((resolve) => {\n if (options.signal?.aborted) {\n resolve(false)\n return\n }\n\n const initial = useGlassesStore.getState().bluetoothClassicConnected\n if (initial) {\n resolve(true)\n return\n }\n\n let settled = false\n let timeout: ReturnType<typeof setTimeout> | null = null\n let unsubscribe: (() => void) | null = null\n let onAbort: (() => void) | null = null\n\n const finish = (value: boolean) => {\n if (settled) return\n settled = true\n if (timeout) clearTimeout(timeout)\n if (unsubscribe) unsubscribe()\n if (onAbort && options.signal) options.signal.removeEventListener(\"abort\", onAbort)\n resolve(value)\n }\n\n if (options.signal) {\n onAbort = () => finish(false)\n options.signal.addEventListener(\"abort\", onAbort)\n }\n\n unsubscribe = useGlassesStore.subscribe(\n (s) => s.bluetoothClassicConnected,\n (connected) => {\n if (connected) finish(true)\n },\n )\n\n timeout = setTimeout(() => {\n finish(useGlassesStore.getState().bluetoothClassicConnected)\n }, timeoutMs)\n })\n}\n\nexport const pairing = {\n readiness: (): PairingReadinessSnapshot => projectReadiness(),\n onReadiness: (cb: (readiness: PairingReadinessSnapshot) => void): (() => void) => {\n let last = JSON.stringify(projectReadiness())\n return useGlassesStore.subscribe(() => {\n const snap = projectReadiness()\n const key = JSON.stringify(snap)\n if (key === last) return\n last = key\n cb(snap)\n })\n },\n /** Whether the exact wearable or controller selected on the scan screen is ready. */\n targetReady: (options: Pick<PairingReadyWaitOptions, \"deviceModel\" | \"deviceName\">): boolean =>\n projectTargetReady(options),\n /** Subscribe to readiness for the exact selected target, excluding stale devices. */\n onTargetReady: (\n options: Pick<PairingReadyWaitOptions, \"deviceModel\" | \"deviceName\">,\n cb: (ready: boolean) => void,\n ): (() => void) => subscribeTargetReady(options, cb),\n\n // --- pairing-identity lifecycle (the PairingIdentity read-model + the JS-owned\n // identity writes; promotion to `paired` only ever happens natively) ---\n /** The identity lifecycle snapshot: none | pending (chosen, never paired) | paired. */\n identity: (): IdentitySnapshot => projectPairingIdentity(),\n /** Subscribe to identity changes; fires only when the projected snapshot changes.\n * Returns an unsubscribe. */\n onIdentity: (cb: (identity: IdentitySnapshot) => void): (() => void) => subscribePairingIdentity(cb),\n /** Mark the chosen model as the pending selection (the scan-entry write); the\n * host renders it as a finish-pairing affordance until pairing succeeds. */\n markPendingSelection: (model: string) => markPendingSelection(model),\n\n /** Start scanning for nearby glasses. Results land on `searchResults()`/`onFound()`. */\n scan: (model: DeviceModel): Promise<void> => BluetoothSdk.startScan(model),\n /** Whether a scan is currently in progress. */\n scanning: (): boolean => useCoreStore.getState().searching,\n /** Subscribe to scan-in-progress changes; fires only when it changes. Returns an unsubscribe. */\n onScanning: (cb: (scanning: boolean) => void): (() => void) => useCoreStore.subscribe((s) => s.searching, cb),\n /** Whether a controller scan is currently in progress. */\n scanningController: (): boolean => useCoreStore.getState().searchingController,\n /** Subscribe to controller-scan-in-progress changes; fires only when it changes. Returns an unsubscribe. */\n onScanningController: (cb: (scanning: boolean) => void): (() => void) =>\n useCoreStore.subscribe((s) => s.searchingController, cb),\n /** Whether the phone has another Bluetooth (audio) device connected — the BT Classic pairing hint. */\n otherBtConnected: (): boolean => useCoreStore.getState().otherBtConnected,\n /** Subscribe to other-Bluetooth-device-connected changes; fires only when it changes. Returns an unsubscribe. */\n onOtherBtConnected: (cb: (connected: boolean) => void): (() => void) =>\n useCoreStore.subscribe((s) => s.otherBtConnected, cb),\n /** The current scan results (snapshot). */\n // Copy the array AND each entry: neither the list nor the Device objects may\n // leak as mutable references into the store.\n searchResults: () => useCoreStore.getState().searchResults.map((result) => ({...result})),\n /** Subscribe to scan-result changes; fires only when they change. Returns an unsubscribe. */\n onFound: (cb: (results: Device[]) => void): (() => void) => {\n let last = JSON.stringify(useCoreStore.getState().searchResults)\n return useCoreStore.subscribe(() => {\n const results = useCoreStore.getState().searchResults\n const key = JSON.stringify(results)\n if (key === last) return\n last = key\n // Copy like searchResults(): listeners must not mutate shared scan state.\n cb(results.map((result) => ({...result})))\n })\n },\n\n /**\n * Pair with (connect to) a discovered device. Two-phase identity: the connect\n * attempt only marks the device as pending (`saveAsDefault: false` — no eager\n * default-device write); the native layer promotes it to the default wearable\n * when pairing actually succeeds (handleDeviceReady), so an abandoned or\n * failed attempt can't leave a default identity with no paired device behind.\n */\n pair: async (device: Device, options?: ConnectOptions): Promise<void> => {\n await pushAllBluetoothSettings()\n return BluetoothSdk.connect(device, {...options, saveAsDefault: false})\n },\n /** Set a device as the default for subsequent `glasses.connectDefault()`. */\n setDefault: (device: Device | null): Promise<void> => BluetoothSdk.setDefaultDevice(device),\n /**\n * Prime the native Bluetooth Classic audio watcher with the picked device.\n * The iOS Mentra Live flow pairs Classic audio BEFORE any BLE connect\n * exists, and native detects the pairing by matching the connected audio\n * route against its device_name — which two-phase identity no longer sets\n * at selection time. This is native-only routing state (device_name has no\n * native→JS echo): the phone's persisted identity is untouched, and\n * promotion still happens only at pairing success.\n */\n setBluetoothClassicTarget: (device: Device): Promise<void> =>\n BluetoothSdk.updateBluetoothSettings({device_name: device.name}),\n /**\n * Abandon an in-flight pairing attempt (back-out, failure retry, conflict\n * retry) without destroying an existing pairing. The decision comes from the\n * LIVE hydrated default-device read — never from flow-entry state, because a\n * pairing can PROMOTE while the flow is open (the glasses finish pairing\n * even as the user backs out of the UI), and an entry snapshot would forget\n * that brand-new pairing:\n * - Default device exists and glasses are connected (nothing in flight — an\n * attempt drops the link first): stop the scan, touch nothing else.\n * - Default device exists with an attempt in flight: cancel it, then re-seed\n * the native identity from the phone's persisted settings — the attempt's\n * connect-by-name overwrote the native device_name, so a later\n * connectDefault() would otherwise target the abandoned device.\n * - No default device (genuinely unpaired attempt): also forget, clearing\n * the partial native pairing state.\n * The read fails OPEN to \"preserve\" — a transient failure must never wipe a\n * real pairing. The `pending_wearable` marker is deliberately left alone —\n * the host renders it as a finish-pairing affordance.\n */\n abandonAttempt: async (): Promise<void> => {\n const nativeHasDefault = await hasDefaultDevice().catch(() => true)\n if (nativeHasDefault && isGlassesConnected(useGlassesStore.getState().connection)) {\n // Still connected means no connect attempt is in flight (an attempt drops\n // the existing link first): the user browsed the scan and backed out.\n // Stop the scan and leave the live pairing untouched.\n console.log(\"PairingIdentity: abandonAttempt — pairing intact and connected; stopping scan only\")\n await BluetoothSdk.stopScan()\n return\n }\n if (projectPairingIdentity().kind === \"paired\") {\n // The persisted settings describe a COMPLETE pairing: restore it to\n // native (the attempt's connect-by-name overwrote the native\n // device_name; and if native somehow lost its default entirely, this\n // repairs the divergence instead of forgetting a real pairing).\n console.log(\"PairingIdentity: abandonAttempt — preserving pairing; attempt cancelled, native identity re-seeded\")\n await BluetoothSdk.disconnect()\n await pushAllBluetoothSettings()\n return\n }\n if (nativeHasDefault) {\n // Mid-relay: native promoted and its echoes are still landing — the\n // incomplete JS snapshot must not be pushed over the fresher native\n // identity (the on-connect replay's race). Native holds the truth.\n console.log(\"PairingIdentity: abandonAttempt — preserving pairing; JS identity mid-relay, native kept as-is\")\n await BluetoothSdk.disconnect()\n return\n }\n // Partial attempt: forget owns teardown. Do NOT disconnect() first — that\n // nulls the MentraLive SGC and used to skip Classic removeBond, leaving\n // IBRT ACL up so glasses never re-advertise for the next scan.\n console.log(\"PairingIdentity: abandonAttempt — no pairing on either layer; forgetting the partial attempt\")\n await BluetoothSdk.forget()\n },\n\n /** Subscribe to pairing failures; returns an unsubscribe. */\n onPairFailure: (cb: (event: PairFailureEvent) => void): (() => void) => {\n const sub = BluetoothSdk.addListener(\"pair_failure\", cb)\n return () => sub.remove()\n },\n /** Subscribe to glasses-not-ready events during pairing; returns an unsubscribe. */\n onGlassesNotReady: (cb: (event: GlassesNotReadyEvent) => void): (() => void) => {\n const sub = BluetoothSdk.addListener(\"glasses_not_ready\", cb)\n return () => sub.remove()\n },\n /** Wait for paired glasses to finish booting; engine files the timeout diagnostic. */\n waitForReady: waitForReadyDuringPairing,\n /** Wait briefly for Bluetooth Classic to come up after BLE pairing. */\n waitForBluetoothClassic,\n}\n"]}
@@ -1,11 +1,15 @@
1
1
  export declare const phoneNotifications: {
2
- /** The phone's installed apps (package, name, icon). (empty on iOS) */
2
+ /**
3
+ * Apps to show in the notification filter UI (package/bundle id, name, icon).
4
+ * Android: installed apps. iOS: seeded catalog ∪ glasses-reported apps
5
+ * (icons unavailable — iOS offers no cross-app icon lookup).
6
+ */
3
7
  installedApps: () => Promise<Array<{
4
8
  packageName: string;
5
9
  appName: string;
6
10
  icon: string | null;
7
11
  }>>;
8
- /** The per-app blocklist (package names whose notifications are NOT forwarded). */
12
+ /** The per-app blocklist (package/bundle ids whose notifications are NOT forwarded). */
9
13
  blocklist: () => string[];
10
14
  /** Replace the per-app blocklist. */
11
15
  setBlocklist: (packageNames: string[]) => import("typesafe-ts").AsyncResult<void, Error>;
@@ -13,5 +17,11 @@ export declare const phoneNotifications: {
13
17
  hasListenerPermission: () => Promise<boolean>;
14
18
  /** Open the OS settings to grant the notification-listener permission. (no-op on iOS) */
15
19
  requestListenerPermission: () => Promise<boolean> | undefined;
20
+ /**
21
+ * iOS: whether the connected glasses may read the phone's ANCS notification
22
+ * stream (the "Share System Notifications" Bluetooth consent). Always false
23
+ * on Android — Android capture is the notification listener instead.
24
+ */
25
+ ancsAuthorized: () => boolean;
16
26
  };
17
27
  //# sourceMappingURL=phoneNotifications.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"phoneNotifications.d.ts","sourceRoot":"","sources":["../../src/facades/phoneNotifications.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,kBAAkB;IAC7B,uEAAuE;yBACpD,OAAO,CAAC,KAAK,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;KAAC,CAAC,CAAC;IAK9F,mFAAmF;qBACpE,MAAM,EAAE;IAIvB,qCAAqC;iCACR,MAAM,EAAE;IAGrC,qFAAqF;iCAC1D,OAAO,CAAC,OAAO,CAAC;IAI3C,yFAAyF;;CAK1F,CAAA"}
1
+ {"version":3,"file":"phoneNotifications.d.ts","sourceRoot":"","sources":["../../src/facades/phoneNotifications.ts"],"names":[],"mappings":"AAkBA,eAAO,MAAM,kBAAkB;IAC7B;;;;OAIG;yBACgB,OAAO,CAAC,KAAK,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;KAAC,CAAC,CAAC;IAY9F,wFAAwF;qBACzE,MAAM,EAAE;IAIvB,qCAAqC;iCACR,MAAM,EAAE;IAGrC,qFAAqF;iCAC1D,OAAO,CAAC,OAAO,CAAC;IAI3C,yFAAyF;;IAMzF;;;;OAIG;0BACiB,OAAO;CAC5B,CAAA"}
@@ -1,24 +1,38 @@
1
1
  /**
2
2
  * phoneNotifications facade — `engine.phoneNotifications`: forward phone
3
- * notifications to the glasses, with a per-app blocklist. Android-only (the
4
- * NotificationListenerService); the getters return sensible defaults on iOS.
3
+ * notifications to the glasses, with a per-app blocklist.
5
4
  *
6
- * The per-app blocklist is an engine setting (auto-synced to the native
7
- * listener); the installed-apps list + listener permission are CrustModule.
8
- * Capture itself is controlled only by Android notification access and the
9
- * hidden operational kill switch.
5
+ * Android: apps come from the NotificationListenerService's installed-apps
6
+ * enumeration and the blocklist filters capture phone-side.
7
+ * iOS: apps are the seeded catalog plus what the glasses' ANCS client has
8
+ * reported posting notifications (`ios_notification_seen_apps`); the blocklist
9
+ * is mapped onto the G2's on-glass allowlist by PhoneNotificationsSync.
10
+ * Capture itself is controlled by Android notification access / iOS ANCS
11
+ * authorization (`glasses.ancsAuthorized`), not by this facade.
10
12
  */
11
13
  import { Platform } from "react-native";
12
14
  import CrustModule from "@glassly/crust";
13
15
  import { useSettingsStore, SETTINGS } from "../stores/settings";
16
+ import { useGlassesStore } from "../stores/glasses";
17
+ import { getKnownNotificationApps } from "../services/PhoneNotificationsSync";
14
18
  export const phoneNotifications = {
15
- /** The phone's installed apps (package, name, icon). (empty on iOS) */
19
+ /**
20
+ * Apps to show in the notification filter UI (package/bundle id, name, icon).
21
+ * Android: installed apps. iOS: seeded catalog ∪ glasses-reported apps
22
+ * (icons unavailable — iOS offers no cross-app icon lookup).
23
+ */
16
24
  installedApps: () => {
17
- if (Platform.OS !== "android")
25
+ if (Platform.OS === "android")
26
+ return CrustModule.getInstalledApps();
27
+ if (Platform.OS !== "ios")
18
28
  return Promise.resolve([]);
19
- return CrustModule.getInstalledApps();
29
+ return Promise.resolve(getKnownNotificationApps().map((app) => ({
30
+ packageName: app.appId,
31
+ appName: app.displayName,
32
+ icon: null,
33
+ })));
20
34
  },
21
- /** The per-app blocklist (package names whose notifications are NOT forwarded). */
35
+ /** The per-app blocklist (package/bundle ids whose notifications are NOT forwarded). */
22
36
  blocklist: () => {
23
37
  const b = useSettingsStore.getState().getSetting(SETTINGS.notifications_blocklist.key);
24
38
  return Array.isArray(b) ? b : [];
@@ -37,5 +51,11 @@ export const phoneNotifications = {
37
51
  return;
38
52
  return CrustModule.openNotificationListenerSettings();
39
53
  },
54
+ /**
55
+ * iOS: whether the connected glasses may read the phone's ANCS notification
56
+ * stream (the "Share System Notifications" Bluetooth consent). Always false
57
+ * on Android — Android capture is the notification listener instead.
58
+ */
59
+ ancsAuthorized: () => Boolean(useGlassesStore.getState().ancsAuthorized),
40
60
  };
41
61
  //# sourceMappingURL=phoneNotifications.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"phoneNotifications.js","sourceRoot":"","sources":["../../src/facades/phoneNotifications.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAC,QAAQ,EAAC,MAAM,cAAc,CAAA;AACrC,OAAO,WAAW,MAAM,gBAAgB,CAAA;AACxC,OAAO,EAAC,gBAAgB,EAAE,QAAQ,EAAC,MAAM,oBAAoB,CAAA;AAE7D,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,uEAAuE;IACvE,aAAa,EAAE,GAAgF,EAAE;QAC/F,IAAI,QAAQ,CAAC,EAAE,KAAK,SAAS;YAAE,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QACzD,OAAO,WAAW,CAAC,gBAAgB,EAAE,CAAA;IACvC,CAAC;IAED,mFAAmF;IACnF,SAAS,EAAE,GAAa,EAAE;QACxB,MAAM,CAAC,GAAG,gBAAgB,CAAC,QAAQ,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,uBAAuB,CAAC,GAAG,CAAC,CAAA;QACtF,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IAClC,CAAC;IACD,qCAAqC;IACrC,YAAY,EAAE,CAAC,YAAsB,EAAE,EAAE,CACvC,gBAAgB,CAAC,QAAQ,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,uBAAuB,CAAC,GAAG,EAAE,YAAY,CAAC;IAE5F,qFAAqF;IACrF,qBAAqB,EAAE,GAAqB,EAAE;QAC5C,IAAI,QAAQ,CAAC,EAAE,KAAK,SAAS;YAAE,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;QAC5D,OAAO,WAAW,CAAC,iCAAiC,EAAE,CAAA;IACxD,CAAC;IACD,yFAAyF;IACzF,yBAAyB,EAAE,GAAG,EAAE;QAC9B,IAAI,QAAQ,CAAC,EAAE,KAAK,SAAS;YAAE,OAAM;QACrC,OAAO,WAAW,CAAC,gCAAgC,EAAE,CAAA;IACvD,CAAC;CACF,CAAA","sourcesContent":["/**\n * phoneNotifications facade — `engine.phoneNotifications`: forward phone\n * notifications to the glasses, with a per-app blocklist. Android-only (the\n * NotificationListenerService); the getters return sensible defaults on iOS.\n *\n * The per-app blocklist is an engine setting (auto-synced to the native\n * listener); the installed-apps list + listener permission are CrustModule.\n * Capture itself is controlled only by Android notification access and the\n * hidden operational kill switch.\n */\nimport {Platform} from \"react-native\"\nimport CrustModule from \"@glassly/crust\"\nimport {useSettingsStore, SETTINGS} from \"../stores/settings\"\n\nexport const phoneNotifications = {\n /** The phone's installed apps (package, name, icon). (empty on iOS) */\n installedApps: (): Promise<Array<{packageName: string; appName: string; icon: string | null}>> => {\n if (Platform.OS !== \"android\") return Promise.resolve([])\n return CrustModule.getInstalledApps()\n },\n\n /** The per-app blocklist (package names whose notifications are NOT forwarded). */\n blocklist: (): string[] => {\n const b = useSettingsStore.getState().getSetting(SETTINGS.notifications_blocklist.key)\n return Array.isArray(b) ? b : []\n },\n /** Replace the per-app blocklist. */\n setBlocklist: (packageNames: string[]) =>\n useSettingsStore.getState().setSetting(SETTINGS.notifications_blocklist.key, packageNames),\n\n /** Does the app have the Android notification-listener permission? (false on iOS) */\n hasListenerPermission: (): Promise<boolean> => {\n if (Platform.OS !== \"android\") return Promise.resolve(false)\n return CrustModule.hasNotificationListenerPermission()\n },\n /** Open the OS settings to grant the notification-listener permission. (no-op on iOS) */\n requestListenerPermission: () => {\n if (Platform.OS !== \"android\") return\n return CrustModule.openNotificationListenerSettings()\n },\n}\n"]}
1
+ {"version":3,"file":"phoneNotifications.js","sourceRoot":"","sources":["../../src/facades/phoneNotifications.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAC,QAAQ,EAAC,MAAM,cAAc,CAAA;AACrC,OAAO,WAAW,MAAM,gBAAgB,CAAA;AACxC,OAAO,EAAC,gBAAgB,EAAE,QAAQ,EAAC,MAAM,oBAAoB,CAAA;AAC7D,OAAO,EAAC,eAAe,EAAC,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAC,wBAAwB,EAAC,MAAM,oCAAoC,CAAA;AAE3E,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC;;;;OAIG;IACH,aAAa,EAAE,GAAgF,EAAE;QAC/F,IAAI,QAAQ,CAAC,EAAE,KAAK,SAAS;YAAE,OAAO,WAAW,CAAC,gBAAgB,EAAE,CAAA;QACpE,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK;YAAE,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QACrD,OAAO,OAAO,CAAC,OAAO,CACpB,wBAAwB,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YACvC,WAAW,EAAE,GAAG,CAAC,KAAK;YACtB,OAAO,EAAE,GAAG,CAAC,WAAW;YACxB,IAAI,EAAE,IAAI;SACX,CAAC,CAAC,CACJ,CAAA;IACH,CAAC;IAED,wFAAwF;IACxF,SAAS,EAAE,GAAa,EAAE;QACxB,MAAM,CAAC,GAAG,gBAAgB,CAAC,QAAQ,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,uBAAuB,CAAC,GAAG,CAAC,CAAA;QACtF,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IAClC,CAAC;IACD,qCAAqC;IACrC,YAAY,EAAE,CAAC,YAAsB,EAAE,EAAE,CACvC,gBAAgB,CAAC,QAAQ,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,uBAAuB,CAAC,GAAG,EAAE,YAAY,CAAC;IAE5F,qFAAqF;IACrF,qBAAqB,EAAE,GAAqB,EAAE;QAC5C,IAAI,QAAQ,CAAC,EAAE,KAAK,SAAS;YAAE,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;QAC5D,OAAO,WAAW,CAAC,iCAAiC,EAAE,CAAA;IACxD,CAAC;IACD,yFAAyF;IACzF,yBAAyB,EAAE,GAAG,EAAE;QAC9B,IAAI,QAAQ,CAAC,EAAE,KAAK,SAAS;YAAE,OAAM;QACrC,OAAO,WAAW,CAAC,gCAAgC,EAAE,CAAA;IACvD,CAAC;IAED;;;;OAIG;IACH,cAAc,EAAE,GAAY,EAAE,CAAC,OAAO,CAAC,eAAe,CAAC,QAAQ,EAAE,CAAC,cAAc,CAAC;CAClF,CAAA","sourcesContent":["/**\n * phoneNotifications facade — `engine.phoneNotifications`: forward phone\n * notifications to the glasses, with a per-app blocklist.\n *\n * Android: apps come from the NotificationListenerService's installed-apps\n * enumeration and the blocklist filters capture phone-side.\n * iOS: apps are the seeded catalog plus what the glasses' ANCS client has\n * reported posting notifications (`ios_notification_seen_apps`); the blocklist\n * is mapped onto the G2's on-glass allowlist by PhoneNotificationsSync.\n * Capture itself is controlled by Android notification access / iOS ANCS\n * authorization (`glasses.ancsAuthorized`), not by this facade.\n */\nimport {Platform} from \"react-native\"\nimport CrustModule from \"@glassly/crust\"\nimport {useSettingsStore, SETTINGS} from \"../stores/settings\"\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {getKnownNotificationApps} from \"../services/PhoneNotificationsSync\"\n\nexport const phoneNotifications = {\n /**\n * Apps to show in the 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 */\n installedApps: (): Promise<Array<{packageName: string; appName: string; icon: string | null}>> => {\n if (Platform.OS === \"android\") return CrustModule.getInstalledApps()\n if (Platform.OS !== \"ios\") return Promise.resolve([])\n return Promise.resolve(\n getKnownNotificationApps().map((app) => ({\n packageName: app.appId,\n appName: app.displayName,\n icon: null,\n })),\n )\n },\n\n /** The per-app blocklist (package/bundle ids whose notifications are NOT forwarded). */\n blocklist: (): string[] => {\n const b = useSettingsStore.getState().getSetting(SETTINGS.notifications_blocklist.key)\n return Array.isArray(b) ? b : []\n },\n /** Replace the per-app blocklist. */\n setBlocklist: (packageNames: string[]) =>\n useSettingsStore.getState().setSetting(SETTINGS.notifications_blocklist.key, packageNames),\n\n /** Does the app have the Android notification-listener permission? (false on iOS) */\n hasListenerPermission: (): Promise<boolean> => {\n if (Platform.OS !== \"android\") return Promise.resolve(false)\n return CrustModule.hasNotificationListenerPermission()\n },\n /** Open the OS settings to grant the notification-listener permission. (no-op on iOS) */\n requestListenerPermission: () => {\n if (Platform.OS !== \"android\") return\n return CrustModule.openNotificationListenerSettings()\n },\n\n /**\n * iOS: whether the connected glasses may read the phone's ANCS notification\n * stream (the \"Share System Notifications\" Bluetooth consent). Always false\n * on Android — Android capture is the notification listener instead.\n */\n ancsAuthorized: (): boolean => Boolean(useGlassesStore.getState().ancsAuthorized),\n}\n"]}
@@ -1,4 +1,4 @@
1
- import type { SpeakerEnrollProgressEvent } from "@glassly/bluetooth-sdk/internal";
1
+ import type { SpeakerEnrollProgressEvent } from "@glassly/bluetooth-sdk";
2
2
  import { type SpeakerIdModelProgress } from "../services/SpeakerIdModelManager";
3
3
  import type { SpeakerProfile } from "../services/SpeakerProfileStore";
4
4
  export type EnrollOutcome = {
@@ -1 +1 @@
1
- {"version":3,"file":"speakers.d.ts","sourceRoot":"","sources":["../../src/facades/speakers.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAC,0BAA0B,EAA2B,MAAM,iCAAiC,CAAA;AAEzG,OAA8B,EAAC,KAAK,sBAAsB,EAAC,MAAM,mCAAmC,CAAA;AACpG,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,iCAAiC,CAAA;AAWnE,MAAM,MAAM,aAAa,GACrB;IAAC,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,cAAc,CAAA;CAAC,GACnC;IAAC,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,cAAc,GAAG,WAAW,GAAG,mBAAmB,GAAG,aAAa,GAAG,WAAW,GAAG,MAAM,CAAA;CAAC,CAAA;AAqElH,eAAO,MAAM,QAAQ;IACnB,qEAAqE;mBAClD,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAIjD,4CAA4C;0BACtB,MAAM,IAAI,KAAG,CAAC,MAAM,IAAI,CAAC;IAC/C;;;OAGG;wBACuB;QAAC,mBAAmB,CAAC,EAAE,MAAM,CAAA;KAAC,KAAG,OAAO,CAAC,aAAa,CAAC;IAUjF,wEAAwE;yBAC7C;QAAC,mBAAmB,CAAC,EAAE,MAAM,CAAA;KAAC,KAAG,OAAO,CAAC,aAAa,CAAC;IAKlF,iFAAiF;sBAC3D,OAAO,CAAC,OAAO,CAAC;IAKtC,uDAAuD;4BACjC,IAAI;IAG1B,8DAA8D;iCACjC,CAAC,QAAQ,EAAE,0BAA0B,KAAK,IAAI,KAAG,CAAC,MAAM,IAAI,CAAC;IAI1F,2DAA2D;;2BAExC,OAAO,CAAC,OAAO,CAAC;yBAClB,MAAM;gCACG,CAAC,CAAC,EAAE,sBAAsB,KAAK,IAAI,KAAG,OAAO,CAAC,IAAI,CAAC;8BAEvD,OAAO,CAAC,IAAI,CAAC;;CAEpC,CAAA"}
1
+ {"version":3,"file":"speakers.d.ts","sourceRoot":"","sources":["../../src/facades/speakers.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAC,0BAA0B,EAA2B,MAAM,wBAAwB,CAAA;AAEhG,OAA8B,EAAC,KAAK,sBAAsB,EAAC,MAAM,mCAAmC,CAAA;AACpG,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,iCAAiC,CAAA;AAWnE,MAAM,MAAM,aAAa,GACrB;IAAC,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,cAAc,CAAA;CAAC,GACnC;IAAC,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,cAAc,GAAG,WAAW,GAAG,mBAAmB,GAAG,aAAa,GAAG,WAAW,GAAG,MAAM,CAAA;CAAC,CAAA;AAqElH,eAAO,MAAM,QAAQ;IACnB,qEAAqE;mBAClD,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAIjD,4CAA4C;0BACtB,MAAM,IAAI,KAAG,CAAC,MAAM,IAAI,CAAC;IAC/C;;;OAGG;wBACuB;QAAC,mBAAmB,CAAC,EAAE,MAAM,CAAA;KAAC,KAAG,OAAO,CAAC,aAAa,CAAC;IAUjF,wEAAwE;yBAC7C;QAAC,mBAAmB,CAAC,EAAE,MAAM,CAAA;KAAC,KAAG,OAAO,CAAC,aAAa,CAAC;IAKlF,iFAAiF;sBAC3D,OAAO,CAAC,OAAO,CAAC;IAKtC,uDAAuD;4BACjC,IAAI;IAG1B,8DAA8D;iCACjC,CAAC,QAAQ,EAAE,0BAA0B,KAAK,IAAI,KAAG,CAAC,MAAM,IAAI,CAAC;IAI1F,2DAA2D;;2BAExC,OAAO,CAAC,OAAO,CAAC;yBAClB,MAAM;gCACG,CAAC,CAAC,EAAE,sBAAsB,KAAK,IAAI,KAAG,OAAO,CAAC,IAAI,CAAC;8BAEvD,OAAO,CAAC,IAAI,CAAC;;CAEpC,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"speakers.js","sourceRoot":"","sources":["../../src/facades/speakers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * speakers facade — `engine.speakers`: the device owner's voice profile for\n * on-device voice recognition (Settings > Voice Recognition).\n *\n * Owner-only surface: the user enrolls their OWN voice once (`enrollSelf`);\n * transcripts are then labeled user / other / unknown. The profile store\n * keeps its generic multi-profile shape underneath, but this facade only\n * exposes the self profile.\n *\n * Enrollment is a guided recording through whichever mic is currently active\n * (native holds the mic open for the session). Raw audio never reaches JS —\n * native returns chunk embeddings, which land in the profile store here; the\n * store push back to native happens via SpeakerIdSync.\n */\nimport BluetoothSdk from \"@glassly/bluetooth-sdk/internal\"\nimport type {SpeakerEnrollProgressEvent, SpeakerEnrollResultEvent} from \"@glassly/bluetooth-sdk/internal\"\n\nimport speakerIdModelManager, {type SpeakerIdModelProgress} from \"../services/SpeakerIdModelManager\"\nimport type {SpeakerProfile} from \"../services/SpeakerProfileStore\"\nimport {speakerProfileStore} from \"../services/speakerProfileStoreInstance\"\n\nconst DEFAULT_TARGET_SPEECH_SECONDS = 20\n\n/**\n * Display name of the self profile. Shown on the memory timeline (which\n * renders `resolvedSpeakerName`); miniapps never see it (PII strip).\n */\nconst SELF_PROFILE_NAME = \"You\"\n\nexport type EnrollOutcome =\n | {ok: true; profile: SpeakerProfile}\n | {ok: false; reason: \"inconsistent\" | \"too_short\" | \"model_unavailable\" | \"unsupported\" | \"cancelled\" | string}\n\nlet activeEnrollment: {cancel: () => void} | null = null\n\n/**\n * Run one enrollment session to completion. Resolves when native reports the\n * result; on success the embeddings are stored (new profile, or appended to\n * `improveProfileId` for \"Improve recognition\").\n */\nasync function runEnrollment(input: {\n name?: string\n improveProfileId?: string\n targetSpeechSeconds?: number\n}): Promise<EnrollOutcome> {\n if (activeEnrollment) return {ok: false, reason: \"already_enrolling\"}\n\n return new Promise<EnrollOutcome>((resolve) => {\n let settled = false\n const settle = (outcome: EnrollOutcome) => {\n if (settled) return\n settled = true\n sub.remove()\n activeEnrollment = null\n console.log(\n outcome.ok\n ? `SpeakerID: enrollment stored — profile \"${outcome.profile.name}\" (${outcome.profile.samples.length} samples)`\n : `SpeakerID: enrollment not stored — ${outcome.reason}`,\n )\n resolve(outcome)\n }\n\n const sub = BluetoothSdk.addListener(\"speaker_enroll_result\", (event: SpeakerEnrollResultEvent) => {\n void (async () => {\n if (!event.ok) {\n settle({ok: false, reason: event.reason ?? \"failed\"})\n return\n }\n const embeddings = event.embeddings ?? []\n const source = event.source === \"glasses\" ? \"glasses\" : \"phone\"\n try {\n if (input.improveProfileId) {\n const profile = await speakerProfileStore.addSamples(input.improveProfileId, embeddings, source)\n if (profile) settle({ok: true, profile})\n else settle({ok: false, reason: \"profile_not_found\"})\n } else {\n const profile = await speakerProfileStore.add(input.name ?? \"\", embeddings, source)\n settle({ok: true, profile})\n }\n } catch (err) {\n settle({ok: false, reason: err instanceof Error ? err.message : \"store_failed\"})\n }\n })()\n })\n\n activeEnrollment = {\n cancel: () => {\n void BluetoothSdk.cancelSpeakerEnrollment().catch(() => {})\n settle({ok: false, reason: \"cancelled\"})\n },\n }\n\n void BluetoothSdk.startSpeakerEnrollment({\n targetSpeechSeconds: input.targetSpeechSeconds ?? DEFAULT_TARGET_SPEECH_SECONDS,\n }).catch((err) => {\n settle({ok: false, reason: err instanceof Error ? err.message : \"start_failed\"})\n })\n })\n}\n\nexport const speakers = {\n /** The owner's enrolled voice profile, or null before enrollment. */\n getSelf: async (): Promise<SpeakerProfile | null> => {\n const profiles = await speakerProfileStore.list()\n return profiles[0] ?? null\n },\n /** Subscribe to profile-store mutations. */\n onChanged: (listener: () => void): (() => void) => speakerProfileStore.onChanged(listener),\n /**\n * Enroll (or re-enroll) the owner's voice via guided recording on the\n * active mic. Replaces any existing self profile on success.\n */\n enrollSelf: async (opts?: {targetSpeechSeconds?: number}): Promise<EnrollOutcome> => {\n const outcome = await runEnrollment({name: SELF_PROFILE_NAME, targetSpeechSeconds: opts?.targetSpeechSeconds})\n if (outcome.ok) {\n // Drop any previous self profile — one owner voice at a time. Done\n // after the new profile is stored so a failed session can't delete.\n const stale = (await speakerProfileStore.list()).filter((p) => p.id !== outcome.profile.id)\n for (const profile of stale) await speakerProfileStore.remove(profile.id)\n }\n return outcome\n },\n /** \"Improve recognition\": record more samples onto the self profile. */\n improveSelf: async (opts?: {targetSpeechSeconds?: number}): Promise<EnrollOutcome> => {\n const self = await speakers.getSelf()\n if (!self) return {ok: false, reason: \"not_enrolled\"}\n return runEnrollment({improveProfileId: self.id, targetSpeechSeconds: opts?.targetSpeechSeconds})\n },\n /** Delete the self profile (also drops it from native via store-change push). */\n removeSelf: async (): Promise<boolean> => {\n const self = await speakers.getSelf()\n if (!self) return false\n return speakerProfileStore.remove(self.id)\n },\n /** Cancel the in-flight enrollment session, if any. */\n cancelEnrollment: (): void => {\n activeEnrollment?.cancel()\n },\n /** Live recording meter + progress ring during enrollment. */\n onEnrollProgress: (listener: (progress: SpeakerEnrollProgressEvent) => void): (() => void) => {\n const sub = BluetoothSdk.addListener(\"speaker_enroll_progress\", listener)\n return () => sub.remove()\n },\n /** Embedding-model delivery (one-time ~28 MB download). */\n model: {\n isAvailable: (): Promise<boolean> => speakerIdModelManager.isModelAvailable(),\n sizeBytes: (): number => speakerIdModelManager.sizeBytes,\n download: (onProgress?: (p: SpeakerIdModelProgress) => void): Promise<void> =>\n speakerIdModelManager.downloadModel(onProgress),\n cancelDownload: (): Promise<void> => speakerIdModelManager.cancelDownload(),\n },\n}\n"]}
1
+ {"version":3,"file":"speakers.js","sourceRoot":"","sources":["../../src/facades/speakers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * speakers facade — `engine.speakers`: the device owner's voice profile for\n * on-device voice recognition (Settings > Voice Recognition).\n *\n * Owner-only surface: the user enrolls their OWN voice once (`enrollSelf`);\n * transcripts are then labeled user / other / unknown. The profile store\n * keeps its generic multi-profile shape underneath, but this facade only\n * exposes the self profile.\n *\n * Enrollment is a guided recording through whichever mic is currently active\n * (native holds the mic open for the session). Raw audio never reaches JS —\n * native returns chunk embeddings, which land in the profile store here; the\n * store push back to native happens via SpeakerIdSync.\n */\nimport BluetoothSdk from \"@glassly/bluetooth-sdk/internal\"\nimport type {SpeakerEnrollProgressEvent, SpeakerEnrollResultEvent} from \"@glassly/bluetooth-sdk\"\n\nimport speakerIdModelManager, {type SpeakerIdModelProgress} from \"../services/SpeakerIdModelManager\"\nimport type {SpeakerProfile} from \"../services/SpeakerProfileStore\"\nimport {speakerProfileStore} from \"../services/speakerProfileStoreInstance\"\n\nconst DEFAULT_TARGET_SPEECH_SECONDS = 20\n\n/**\n * Display name of the self profile. Shown on the memory timeline (which\n * renders `resolvedSpeakerName`); miniapps never see it (PII strip).\n */\nconst SELF_PROFILE_NAME = \"You\"\n\nexport type EnrollOutcome =\n | {ok: true; profile: SpeakerProfile}\n | {ok: false; reason: \"inconsistent\" | \"too_short\" | \"model_unavailable\" | \"unsupported\" | \"cancelled\" | string}\n\nlet activeEnrollment: {cancel: () => void} | null = null\n\n/**\n * Run one enrollment session to completion. Resolves when native reports the\n * result; on success the embeddings are stored (new profile, or appended to\n * `improveProfileId` for \"Improve recognition\").\n */\nasync function runEnrollment(input: {\n name?: string\n improveProfileId?: string\n targetSpeechSeconds?: number\n}): Promise<EnrollOutcome> {\n if (activeEnrollment) return {ok: false, reason: \"already_enrolling\"}\n\n return new Promise<EnrollOutcome>((resolve) => {\n let settled = false\n const settle = (outcome: EnrollOutcome) => {\n if (settled) return\n settled = true\n sub.remove()\n activeEnrollment = null\n console.log(\n outcome.ok\n ? `SpeakerID: enrollment stored — profile \"${outcome.profile.name}\" (${outcome.profile.samples.length} samples)`\n : `SpeakerID: enrollment not stored — ${outcome.reason}`,\n )\n resolve(outcome)\n }\n\n const sub = BluetoothSdk.addListener(\"speaker_enroll_result\", (event: SpeakerEnrollResultEvent) => {\n void (async () => {\n if (!event.ok) {\n settle({ok: false, reason: event.reason ?? \"failed\"})\n return\n }\n const embeddings = event.embeddings ?? []\n const source = event.source === \"glasses\" ? \"glasses\" : \"phone\"\n try {\n if (input.improveProfileId) {\n const profile = await speakerProfileStore.addSamples(input.improveProfileId, embeddings, source)\n if (profile) settle({ok: true, profile})\n else settle({ok: false, reason: \"profile_not_found\"})\n } else {\n const profile = await speakerProfileStore.add(input.name ?? \"\", embeddings, source)\n settle({ok: true, profile})\n }\n } catch (err) {\n settle({ok: false, reason: err instanceof Error ? err.message : \"store_failed\"})\n }\n })()\n })\n\n activeEnrollment = {\n cancel: () => {\n void BluetoothSdk.cancelSpeakerEnrollment().catch(() => {})\n settle({ok: false, reason: \"cancelled\"})\n },\n }\n\n void BluetoothSdk.startSpeakerEnrollment({\n targetSpeechSeconds: input.targetSpeechSeconds ?? DEFAULT_TARGET_SPEECH_SECONDS,\n }).catch((err) => {\n settle({ok: false, reason: err instanceof Error ? err.message : \"start_failed\"})\n })\n })\n}\n\nexport const speakers = {\n /** The owner's enrolled voice profile, or null before enrollment. */\n getSelf: async (): Promise<SpeakerProfile | null> => {\n const profiles = await speakerProfileStore.list()\n return profiles[0] ?? null\n },\n /** Subscribe to profile-store mutations. */\n onChanged: (listener: () => void): (() => void) => speakerProfileStore.onChanged(listener),\n /**\n * Enroll (or re-enroll) the owner's voice via guided recording on the\n * active mic. Replaces any existing self profile on success.\n */\n enrollSelf: async (opts?: {targetSpeechSeconds?: number}): Promise<EnrollOutcome> => {\n const outcome = await runEnrollment({name: SELF_PROFILE_NAME, targetSpeechSeconds: opts?.targetSpeechSeconds})\n if (outcome.ok) {\n // Drop any previous self profile — one owner voice at a time. Done\n // after the new profile is stored so a failed session can't delete.\n const stale = (await speakerProfileStore.list()).filter((p) => p.id !== outcome.profile.id)\n for (const profile of stale) await speakerProfileStore.remove(profile.id)\n }\n return outcome\n },\n /** \"Improve recognition\": record more samples onto the self profile. */\n improveSelf: async (opts?: {targetSpeechSeconds?: number}): Promise<EnrollOutcome> => {\n const self = await speakers.getSelf()\n if (!self) return {ok: false, reason: \"not_enrolled\"}\n return runEnrollment({improveProfileId: self.id, targetSpeechSeconds: opts?.targetSpeechSeconds})\n },\n /** Delete the self profile (also drops it from native via store-change push). */\n removeSelf: async (): Promise<boolean> => {\n const self = await speakers.getSelf()\n if (!self) return false\n return speakerProfileStore.remove(self.id)\n },\n /** Cancel the in-flight enrollment session, if any. */\n cancelEnrollment: (): void => {\n activeEnrollment?.cancel()\n },\n /** Live recording meter + progress ring during enrollment. */\n onEnrollProgress: (listener: (progress: SpeakerEnrollProgressEvent) => void): (() => void) => {\n const sub = BluetoothSdk.addListener(\"speaker_enroll_progress\", listener)\n return () => sub.remove()\n },\n /** Embedding-model delivery (one-time ~28 MB download). */\n model: {\n isAvailable: (): Promise<boolean> => speakerIdModelManager.isModelAvailable(),\n sizeBytes: (): number => speakerIdModelManager.sizeBytes,\n download: (onProgress?: (p: SpeakerIdModelProgress) => void): Promise<void> =>\n speakerIdModelManager.downloadModel(onProgress),\n cancelDownload: (): Promise<void> => speakerIdModelManager.cancelDownload(),\n },\n}\n"]}
@@ -1,22 +1,23 @@
1
1
  import sttModelManager from "../services/STTModelManager";
2
2
  import ttsModelManager from "../services/TTSModelManager";
3
- import offlineSpeechModelService from "../services/OfflineSpeechModelService";
3
+ import { type DownloadStatus as OfflineModelDownloadStatus } from "../services/OfflineSpeechModelService";
4
4
  import sonioxDirectService from "../services/soniox/SonioxDirectService";
5
+ import type { DownloadProgress as SttDownloadProgress, ExtractionProgress as SttExtractionProgress, LanguageConfig as SttLanguageConfig, LanguageInfo as SttLanguageInfo } from "../services/STTModelManager";
5
6
  export declare const speech: {
6
7
  /** Restart the glasses' on-device transcriber (e.g. after switching STT language). */
7
8
  restartTranscriber: () => Promise<void>;
8
9
  stt: {
9
10
  currentLanguage: () => string;
10
- languages: () => import("../internal").SttLanguageConfig[];
11
- languageInfo: () => Promise<import("../internal").SttLanguageInfo[]>;
12
- download: (code?: string | undefined, onProgress?: ((progress: import("../internal").SttDownloadProgress) => void) | undefined, onExtractionProgress?: ((progress: import("../internal").SttExtractionProgress) => void) | undefined) => Promise<void>;
11
+ languages: () => SttLanguageConfig[];
12
+ languageInfo: () => Promise<SttLanguageInfo[]>;
13
+ download: (code?: string | undefined, onProgress?: ((progress: SttDownloadProgress) => void) | undefined, onExtractionProgress?: ((progress: SttExtractionProgress) => void) | undefined) => Promise<void>;
13
14
  activate: (code: string) => void;
14
15
  cancelDownload: () => Promise<void>;
15
16
  deleteModel: (code?: string | undefined) => Promise<void>;
16
17
  /** Current auto-download status of the offline STT model. */
17
- status: () => import("../internal").OfflineModelDownloadStatus | null;
18
+ status: () => OfflineModelDownloadStatus | null;
18
19
  /** Subscribe to auto-download status changes; returns an unsubscribe. */
19
- onStatusChanged: (listener: (status: import("../internal").OfflineModelDownloadStatus | null) => void) => () => void;
20
+ onStatusChanged: (listener: (status: OfflineModelDownloadStatus | null) => void) => (() => void);
20
21
  };
21
22
  /** Phone → Soniox direct transcription (user-supplied API key, Settings > Keys). */
22
23
  sonioxDirect: {
@@ -1 +1 @@
1
- {"version":3,"file":"speech.d.ts","sourceRoot":"","sources":["../../src/facades/speech.ts"],"names":[],"mappings":"AAOA,OAAO,eAAe,MAAM,6BAA6B,CAAA;AACzD,OAAO,eAAe,MAAM,6BAA6B,CAAA;AACzD,OAAO,yBAAyB,MAAM,uCAAuC,CAAA;AAC7E,OAAO,mBAAmB,MAAM,wCAAwC,CAAA;AAExE,eAAO,MAAM,MAAM;IACjB,sFAAsF;8BAC9D,OAAO,CAAC,IAAI,CAAC;;;;;;yBAMlB,MAAM;;;QAGvB,6DAA6D;;QAE7D,yEAAyE;;;IAI3E,oFAAoF;;;QAGlF,2DAA2D;;QAG3D,4DAA4D;;;;;;;;;;;;;yBAS3C,MAAM;;;;CAI1B,CAAA"}
1
+ {"version":3,"file":"speech.d.ts","sourceRoot":"","sources":["../../src/facades/speech.ts"],"names":[],"mappings":"AAOA,OAAO,eAAe,MAAM,6BAA6B,CAAA;AACzD,OAAO,eAAe,MAAM,6BAA6B,CAAA;AACzD,OAAkC,EAChC,KAAK,cAAc,IAAI,0BAA0B,EAClD,MAAM,uCAAuC,CAAA;AAC9C,OAAO,mBAAmB,MAAM,wCAAwC,CAAA;AACxE,OAAO,KAAK,EACV,gBAAgB,IAAI,mBAAmB,EACvC,kBAAkB,IAAI,qBAAqB,EAC3C,cAAc,IAAI,iBAAiB,EACnC,YAAY,IAAI,eAAe,EAChC,MAAM,6BAA6B,CAAA;AAEpC,eAAO,MAAM,MAAM;IACjB,sFAAsF;8BAC9D,OAAO,CAAC,IAAI,CAAC;;;yBAGpB,iBAAiB,EAAE;4BAChB,OAAO,CAAC,eAAe,EAAE,CAAC;uEAIhB,mBAAmB,KAAK,IAAI,kDAClB,qBAAqB,KAAK,IAAI,kBAEjE,OAAO,CAAC,IAAI,CAAC;yBACC,MAAM;;;QAGvB,6DAA6D;sBACjD,0BAA0B,GAAG,IAAI;QAC7C,yEAAyE;oCAC7C,CAAC,MAAM,EAAE,0BAA0B,GAAG,IAAI,KAAK,IAAI,KAAG,CAAC,MAAM,IAAI,CAAC;;IAGhG,oFAAoF;;;QAGlF,2DAA2D;;QAG3D,4DAA4D;;;;;;;;;;;;;yBAS3C,MAAM;;;;CAI1B,CAAA"}
@@ -23,7 +23,7 @@ export const speech = {
23
23
  /** Current auto-download status of the offline STT model. */
24
24
  status: () => offlineSpeechModelService.getStatus(),
25
25
  /** Subscribe to auto-download status changes; returns an unsubscribe. */
26
- onStatusChanged: (...args) => offlineSpeechModelService.subscribe(...args),
26
+ onStatusChanged: (listener) => offlineSpeechModelService.subscribe(listener),
27
27
  },
28
28
  /** Phone → Soniox direct transcription (user-supplied API key, Settings > Keys). */
29
29
  sonioxDirect: {
@@ -1 +1 @@
1
- {"version":3,"file":"speech.js","sourceRoot":"","sources":["../../src/facades/speech.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,YAAY,MAAM,iCAAiC,CAAA;AAC1D,OAAO,eAAe,MAAM,6BAA6B,CAAA;AACzD,OAAO,eAAe,MAAM,6BAA6B,CAAA;AACzD,OAAO,yBAAyB,MAAM,uCAAuC,CAAA;AAC7E,OAAO,mBAAmB,MAAM,wCAAwC,CAAA;AAExE,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,sFAAsF;IACtF,kBAAkB,EAAE,GAAkB,EAAE,CAAC,YAAY,CAAC,kBAAkB,EAAE;IAC1E,GAAG,EAAE;QACH,eAAe,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,kBAAkB,EAAE;QAC3D,SAAS,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,qBAAqB,EAAE;QACxD,YAAY,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,kBAAkB,EAAE;QACxD,QAAQ,EAAE,CAAC,GAAG,IAAsD,EAAE,EAAE,CAAC,eAAe,CAAC,aAAa,CAAC,GAAG,IAAI,CAAC;QAC/G,QAAQ,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,eAAe,CAAC,kBAAkB,CAAC,IAAI,CAAC;QACpE,cAAc,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,cAAc,EAAE;QACtD,WAAW,EAAE,CAAC,GAAG,IAAoD,EAAE,EAAE,CAAC,eAAe,CAAC,WAAW,CAAC,GAAG,IAAI,CAAC;QAC9G,6DAA6D;QAC7D,MAAM,EAAE,GAAG,EAAE,CAAC,yBAAyB,CAAC,SAAS,EAAE;QACnD,yEAAyE;QACzE,eAAe,EAAE,CAAC,GAAG,IAA4D,EAAE,EAAE,CACnF,yBAAyB,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC;KAC/C;IACD,oFAAoF;IACpF,YAAY,EAAE;QACZ,SAAS,EAAE,GAAG,EAAE,CAAC,mBAAmB,CAAC,SAAS,EAAE;QAChD,2DAA2D;QAC3D,eAAe,EAAE,CAAC,GAAG,IAA4D,EAAE,EAAE,CACnF,mBAAmB,CAAC,eAAe,CAAC,GAAG,IAAI,CAAC;QAC9C,4DAA4D;QAC5D,WAAW,EAAE,CAAC,GAAG,IAA2D,EAAE,EAAE,CAC9E,mBAAmB,CAAC,cAAc,CAAC,GAAG,IAAI,CAAC;KAC9C;IACD,GAAG,EAAE;QACH,eAAe,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,kBAAkB,EAAE;QAC3D,SAAS,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,qBAAqB,EAAE;QACxD,YAAY,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,kBAAkB,EAAE;QACxD,QAAQ,EAAE,CAAC,GAAG,IAAsD,EAAE,EAAE,CAAC,eAAe,CAAC,aAAa,CAAC,GAAG,IAAI,CAAC;QAC/G,QAAQ,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,eAAe,CAAC,kBAAkB,CAAC,IAAI,CAAC;QACpE,cAAc,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,cAAc,EAAE;QACtD,WAAW,EAAE,CAAC,GAAG,IAAoD,EAAE,EAAE,CAAC,eAAe,CAAC,WAAW,CAAC,GAAG,IAAI,CAAC;KAC/G;CACF,CAAA","sourcesContent":["/**\n * speech facade — `engine.speech.{stt,tts}`: on-device STT/TTS model management,\n * wrapping the in-engine model managers. Thin passthrough (args forwarded with\n * exact types via Parameters<>), so the facade never drifts from the managers.\n * STT additionally exposes the auto-download status stream (the TTS path has none).\n */\nimport BluetoothSdk from \"@glassly/bluetooth-sdk/internal\"\nimport sttModelManager from \"../services/STTModelManager\"\nimport ttsModelManager from \"../services/TTSModelManager\"\nimport offlineSpeechModelService from \"../services/OfflineSpeechModelService\"\nimport sonioxDirectService from \"../services/soniox/SonioxDirectService\"\n\nexport const speech = {\n /** Restart the glasses' on-device transcriber (e.g. after switching STT language). */\n restartTranscriber: (): Promise<void> => BluetoothSdk.restartTranscriber(),\n stt: {\n currentLanguage: () => sttModelManager.getCurrentLanguage(),\n languages: () => sttModelManager.getAvailableLanguages(),\n languageInfo: () => sttModelManager.getAllLanguageInfo(),\n download: (...args: Parameters<typeof sttModelManager.downloadModel>) => sttModelManager.downloadModel(...args),\n activate: (code: string) => sttModelManager.setCurrentLanguage(code),\n cancelDownload: () => sttModelManager.cancelDownload(),\n deleteModel: (...args: Parameters<typeof sttModelManager.deleteModel>) => sttModelManager.deleteModel(...args),\n /** Current auto-download status of the offline STT model. */\n status: () => offlineSpeechModelService.getStatus(),\n /** Subscribe to auto-download status changes; returns an unsubscribe. */\n onStatusChanged: (...args: Parameters<typeof offlineSpeechModelService.subscribe>) =>\n offlineSpeechModelService.subscribe(...args),\n },\n /** Phone → Soniox direct transcription (user-supplied API key, Settings > Keys). */\n sonioxDirect: {\n getStatus: () => sonioxDirectService.getStatus(),\n /** Subscribe to status changes; returns an unsubscribe. */\n onStatusChanged: (...args: Parameters<typeof sonioxDirectService.onStatusChanged>) =>\n sonioxDirectService.onStatusChanged(...args),\n /** Probe a key against the Soniox API without saving it. */\n validateKey: (...args: Parameters<typeof sonioxDirectService.validateApiKey>) =>\n sonioxDirectService.validateApiKey(...args),\n },\n tts: {\n currentLanguage: () => ttsModelManager.getCurrentLanguage(),\n languages: () => ttsModelManager.getAvailableLanguages(),\n languageInfo: () => ttsModelManager.getAllLanguageInfo(),\n download: (...args: Parameters<typeof ttsModelManager.downloadModel>) => ttsModelManager.downloadModel(...args),\n activate: (code: string) => ttsModelManager.setCurrentLanguage(code),\n cancelDownload: () => ttsModelManager.cancelDownload(),\n deleteModel: (...args: Parameters<typeof ttsModelManager.deleteModel>) => ttsModelManager.deleteModel(...args),\n },\n}\n"]}
1
+ {"version":3,"file":"speech.js","sourceRoot":"","sources":["../../src/facades/speech.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,YAAY,MAAM,iCAAiC,CAAA;AAC1D,OAAO,eAAe,MAAM,6BAA6B,CAAA;AACzD,OAAO,eAAe,MAAM,6BAA6B,CAAA;AACzD,OAAO,yBAEN,MAAM,uCAAuC,CAAA;AAC9C,OAAO,mBAAmB,MAAM,wCAAwC,CAAA;AAQxE,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,sFAAsF;IACtF,kBAAkB,EAAE,GAAkB,EAAE,CAAC,YAAY,CAAC,kBAAkB,EAAE;IAC1E,GAAG,EAAE;QACH,eAAe,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,kBAAkB,EAAE;QAC3D,SAAS,EAAE,GAAwB,EAAE,CAAC,eAAe,CAAC,qBAAqB,EAAE;QAC7E,YAAY,EAAE,GAA+B,EAAE,CAAC,eAAe,CAAC,kBAAkB,EAAE;QACpF,QAAQ,EAAE,CACR,GAAG,IAIF,EACc,EAAE,CAAC,eAAe,CAAC,aAAa,CAAC,GAAG,IAAI,CAAC;QAC1D,QAAQ,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,eAAe,CAAC,kBAAkB,CAAC,IAAI,CAAC;QACpE,cAAc,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,cAAc,EAAE;QACtD,WAAW,EAAE,CAAC,GAAG,IAAoD,EAAE,EAAE,CAAC,eAAe,CAAC,WAAW,CAAC,GAAG,IAAI,CAAC;QAC9G,6DAA6D;QAC7D,MAAM,EAAE,GAAsC,EAAE,CAAC,yBAAyB,CAAC,SAAS,EAAE;QACtF,yEAAyE;QACzE,eAAe,EAAE,CAAC,QAA6D,EAAgB,EAAE,CAC/F,yBAAyB,CAAC,SAAS,CAAC,QAAQ,CAAC;KAChD;IACD,oFAAoF;IACpF,YAAY,EAAE;QACZ,SAAS,EAAE,GAAG,EAAE,CAAC,mBAAmB,CAAC,SAAS,EAAE;QAChD,2DAA2D;QAC3D,eAAe,EAAE,CAAC,GAAG,IAA4D,EAAE,EAAE,CACnF,mBAAmB,CAAC,eAAe,CAAC,GAAG,IAAI,CAAC;QAC9C,4DAA4D;QAC5D,WAAW,EAAE,CAAC,GAAG,IAA2D,EAAE,EAAE,CAC9E,mBAAmB,CAAC,cAAc,CAAC,GAAG,IAAI,CAAC;KAC9C;IACD,GAAG,EAAE;QACH,eAAe,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,kBAAkB,EAAE;QAC3D,SAAS,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,qBAAqB,EAAE;QACxD,YAAY,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,kBAAkB,EAAE;QACxD,QAAQ,EAAE,CAAC,GAAG,IAAsD,EAAE,EAAE,CAAC,eAAe,CAAC,aAAa,CAAC,GAAG,IAAI,CAAC;QAC/G,QAAQ,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,eAAe,CAAC,kBAAkB,CAAC,IAAI,CAAC;QACpE,cAAc,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,cAAc,EAAE;QACtD,WAAW,EAAE,CAAC,GAAG,IAAoD,EAAE,EAAE,CAAC,eAAe,CAAC,WAAW,CAAC,GAAG,IAAI,CAAC;KAC/G;CACF,CAAA","sourcesContent":["/**\n * speech facade — `engine.speech.{stt,tts}`: on-device STT/TTS model management,\n * wrapping the in-engine model managers. Thin passthrough (args forwarded with\n * exact types via Parameters<>), so the facade never drifts from the managers.\n * STT additionally exposes the auto-download status stream (the TTS path has none).\n */\nimport BluetoothSdk from \"@glassly/bluetooth-sdk/internal\"\nimport sttModelManager from \"../services/STTModelManager\"\nimport ttsModelManager from \"../services/TTSModelManager\"\nimport offlineSpeechModelService, {\n type DownloadStatus as OfflineModelDownloadStatus,\n} from \"../services/OfflineSpeechModelService\"\nimport sonioxDirectService from \"../services/soniox/SonioxDirectService\"\nimport type {\n DownloadProgress as SttDownloadProgress,\n ExtractionProgress as SttExtractionProgress,\n LanguageConfig as SttLanguageConfig,\n LanguageInfo as SttLanguageInfo,\n} from \"../services/STTModelManager\"\n\nexport const speech = {\n /** Restart the glasses' on-device transcriber (e.g. after switching STT language). */\n restartTranscriber: (): Promise<void> => BluetoothSdk.restartTranscriber(),\n stt: {\n currentLanguage: () => sttModelManager.getCurrentLanguage(),\n languages: (): SttLanguageConfig[] => sttModelManager.getAvailableLanguages(),\n languageInfo: (): Promise<SttLanguageInfo[]> => sttModelManager.getAllLanguageInfo(),\n download: (\n ...args: [\n code?: string,\n onProgress?: (progress: SttDownloadProgress) => void,\n onExtractionProgress?: (progress: SttExtractionProgress) => void,\n ]\n ): Promise<void> => sttModelManager.downloadModel(...args),\n activate: (code: string) => sttModelManager.setCurrentLanguage(code),\n cancelDownload: () => sttModelManager.cancelDownload(),\n deleteModel: (...args: Parameters<typeof sttModelManager.deleteModel>) => sttModelManager.deleteModel(...args),\n /** Current auto-download status of the offline STT model. */\n status: (): OfflineModelDownloadStatus | null => offlineSpeechModelService.getStatus(),\n /** Subscribe to auto-download status changes; returns an unsubscribe. */\n onStatusChanged: (listener: (status: OfflineModelDownloadStatus | null) => void): (() => void) =>\n offlineSpeechModelService.subscribe(listener),\n },\n /** Phone → Soniox direct transcription (user-supplied API key, Settings > Keys). */\n sonioxDirect: {\n getStatus: () => sonioxDirectService.getStatus(),\n /** Subscribe to status changes; returns an unsubscribe. */\n onStatusChanged: (...args: Parameters<typeof sonioxDirectService.onStatusChanged>) =>\n sonioxDirectService.onStatusChanged(...args),\n /** Probe a key against the Soniox API without saving it. */\n validateKey: (...args: Parameters<typeof sonioxDirectService.validateApiKey>) =>\n sonioxDirectService.validateApiKey(...args),\n },\n tts: {\n currentLanguage: () => ttsModelManager.getCurrentLanguage(),\n languages: () => ttsModelManager.getAvailableLanguages(),\n languageInfo: () => ttsModelManager.getAllLanguageInfo(),\n download: (...args: Parameters<typeof ttsModelManager.downloadModel>) => ttsModelManager.downloadModel(...args),\n activate: (code: string) => ttsModelManager.setCurrentLanguage(code),\n cancelDownload: () => ttsModelManager.cancelDownload(),\n deleteModel: (...args: Parameters<typeof ttsModelManager.deleteModel>) => ttsModelManager.deleteModel(...args),\n },\n}\n"]}
@@ -0,0 +1,12 @@
1
+ /** Generated by release CI. Do not edit in a release checkout. */
2
+ export interface EngineReleaseMetadata {
3
+ schemaVersion: 1;
4
+ familyBaseVersion: string | null;
5
+ releaseIdentity: string | null;
6
+ releaseSetId: string | null;
7
+ sourceCommit: string | null;
8
+ otaManifestUrl: string | null;
9
+ otaManifestSha256: string | null;
10
+ }
11
+ export declare const ENGINE_RELEASE_METADATA: Readonly<EngineReleaseMetadata>;
12
+ //# sourceMappingURL=releaseMetadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"releaseMetadata.d.ts","sourceRoot":"","sources":["../../src/generated/releaseMetadata.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,MAAM,WAAW,qBAAqB;IACpC,aAAa,EAAE,CAAC,CAAA;IAChB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;CACjC;AAED,eAAO,MAAM,uBAAuB,EAAE,QAAQ,CAAC,qBAAqB,CAQlE,CAAA"}
@@ -0,0 +1,10 @@
1
+ export const ENGINE_RELEASE_METADATA = Object.freeze({
2
+ "schemaVersion": 1,
3
+ "familyBaseVersion": "0.1.0",
4
+ "releaseIdentity": "0.1.0-dev.100",
5
+ "releaseSetId": "glassly-0.1.0-dev.100",
6
+ "sourceCommit": "5759886304d66eec8e3088937b3b650fab1283b7",
7
+ "otaManifestUrl": null,
8
+ "otaManifestSha256": null
9
+ });
10
+ //# sourceMappingURL=releaseMetadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"releaseMetadata.js","sourceRoot":"","sources":["../../src/generated/releaseMetadata.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,MAAM,uBAAuB,GAAoC,MAAM,CAAC,MAAM,CAAC;IACpF,eAAe,EAAE,CAAC;IAClB,mBAAmB,EAAE,OAAO;IAC5B,iBAAiB,EAAE,eAAe;IAClC,cAAc,EAAE,uBAAuB;IACvC,cAAc,EAAE,0CAA0C;IAC1D,gBAAgB,EAAE,IAAI;IACtB,mBAAmB,EAAE,IAAI;CAC1B,CAAC,CAAA","sourcesContent":["/** Generated by release CI. Do not edit in a release checkout. */\nexport interface EngineReleaseMetadata {\n schemaVersion: 1\n familyBaseVersion: string | null\n releaseIdentity: string | null\n releaseSetId: string | null\n sourceCommit: string | null\n otaManifestUrl: string | null\n otaManifestSha256: string | null\n}\n\nexport const ENGINE_RELEASE_METADATA: Readonly<EngineReleaseMetadata> = Object.freeze({\n \"schemaVersion\": 1,\n \"familyBaseVersion\": \"0.1.0\",\n \"releaseIdentity\": \"0.1.0-dev.100\",\n \"releaseSetId\": \"glassly-0.1.0-dev.100\",\n \"sourceCommit\": \"5759886304d66eec8e3088937b3b650fab1283b7\",\n \"otaManifestUrl\": null,\n \"otaManifestSha256\": null\n})\n"]}