@glassly/engine 0.1.0-dev.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1053) hide show
  1. package/README.md +96 -0
  2. package/build/devtools.d.ts +11 -0
  3. package/build/devtools.d.ts.map +1 -0
  4. package/build/devtools.js +13 -0
  5. package/build/devtools.js.map +1 -0
  6. package/build/engine.d.ts +336 -0
  7. package/build/engine.d.ts.map +1 -0
  8. package/build/engine.js +198 -0
  9. package/build/engine.js.map +1 -0
  10. package/build/facades/dev.d.ts +69 -0
  11. package/build/facades/dev.d.ts.map +1 -0
  12. package/build/facades/dev.js +204 -0
  13. package/build/facades/dev.js.map +1 -0
  14. package/build/facades/display.d.ts +21 -0
  15. package/build/facades/display.d.ts.map +1 -0
  16. package/build/facades/display.js +17 -0
  17. package/build/facades/display.js.map +1 -0
  18. package/build/facades/displayMirror.d.ts +15 -0
  19. package/build/facades/displayMirror.d.ts.map +1 -0
  20. package/build/facades/displayMirror.js +29 -0
  21. package/build/facades/displayMirror.js.map +1 -0
  22. package/build/facades/gallery.d.ts +62 -0
  23. package/build/facades/gallery.d.ts.map +1 -0
  24. package/build/facades/gallery.js +61 -0
  25. package/build/facades/gallery.js.map +1 -0
  26. package/build/facades/glasses.d.ts +142 -0
  27. package/build/facades/glasses.d.ts.map +1 -0
  28. package/build/facades/glasses.js +214 -0
  29. package/build/facades/glasses.js.map +1 -0
  30. package/build/facades/glassesSettings.d.ts +13 -0
  31. package/build/facades/glassesSettings.d.ts.map +1 -0
  32. package/build/facades/glassesSettings.js +55 -0
  33. package/build/facades/glassesSettings.js.map +1 -0
  34. package/build/facades/glassesWifi.d.ts +25 -0
  35. package/build/facades/glassesWifi.d.ts.map +1 -0
  36. package/build/facades/glassesWifi.js +57 -0
  37. package/build/facades/glassesWifi.js.map +1 -0
  38. package/build/facades/maps.d.ts +18 -0
  39. package/build/facades/maps.d.ts.map +1 -0
  40. package/build/facades/maps.js +13 -0
  41. package/build/facades/maps.js.map +1 -0
  42. package/build/facades/memory.d.ts +38 -0
  43. package/build/facades/memory.d.ts.map +1 -0
  44. package/build/facades/memory.js +33 -0
  45. package/build/facades/memory.js.map +1 -0
  46. package/build/facades/miniapps.d.ts +47 -0
  47. package/build/facades/miniapps.d.ts.map +1 -0
  48. package/build/facades/miniapps.js +54 -0
  49. package/build/facades/miniapps.js.map +1 -0
  50. package/build/facades/notifications.d.ts +13 -0
  51. package/build/facades/notifications.d.ts.map +1 -0
  52. package/build/facades/notifications.js +12 -0
  53. package/build/facades/notifications.js.map +1 -0
  54. package/build/facades/ota.d.ts +106 -0
  55. package/build/facades/ota.d.ts.map +1 -0
  56. package/build/facades/ota.js +159 -0
  57. package/build/facades/ota.js.map +1 -0
  58. package/build/facades/pairing.d.ts +115 -0
  59. package/build/facades/pairing.d.ts.map +1 -0
  60. package/build/facades/pairing.js +275 -0
  61. package/build/facades/pairing.js.map +1 -0
  62. package/build/facades/permissions.d.ts +33 -0
  63. package/build/facades/permissions.d.ts.map +1 -0
  64. package/build/facades/permissions.js +155 -0
  65. package/build/facades/permissions.js.map +1 -0
  66. package/build/facades/phoneNotifications.d.ts +17 -0
  67. package/build/facades/phoneNotifications.d.ts.map +1 -0
  68. package/build/facades/phoneNotifications.js +41 -0
  69. package/build/facades/phoneNotifications.js.map +1 -0
  70. package/build/facades/providerKeys.d.ts +26 -0
  71. package/build/facades/providerKeys.d.ts.map +1 -0
  72. package/build/facades/providerKeys.js +26 -0
  73. package/build/facades/providerKeys.js.map +1 -0
  74. package/build/facades/reports.d.ts +36 -0
  75. package/build/facades/reports.d.ts.map +1 -0
  76. package/build/facades/reports.js +134 -0
  77. package/build/facades/reports.js.map +1 -0
  78. package/build/facades/session.d.ts +21 -0
  79. package/build/facades/session.d.ts.map +1 -0
  80. package/build/facades/session.js +52 -0
  81. package/build/facades/session.js.map +1 -0
  82. package/build/facades/settings.d.ts +37 -0
  83. package/build/facades/settings.d.ts.map +1 -0
  84. package/build/facades/settings.js +55 -0
  85. package/build/facades/settings.js.map +1 -0
  86. package/build/facades/speakers.d.ts +41 -0
  87. package/build/facades/speakers.d.ts.map +1 -0
  88. package/build/facades/speakers.js +139 -0
  89. package/build/facades/speakers.js.map +1 -0
  90. package/build/facades/speech.d.ts +44 -0
  91. package/build/facades/speech.d.ts.map +1 -0
  92. package/build/facades/speech.js +46 -0
  93. package/build/facades/speech.js.map +1 -0
  94. package/build/index.d.ts +48 -0
  95. package/build/index.d.ts.map +1 -0
  96. package/build/index.js +49 -0
  97. package/build/index.js.map +1 -0
  98. package/build/internal.d.ts +67 -0
  99. package/build/internal.d.ts.map +1 -0
  100. package/build/internal.js +90 -0
  101. package/build/internal.js.map +1 -0
  102. package/build/runtime/bootstrap.d.ts +115 -0
  103. package/build/runtime/bootstrap.d.ts.map +1 -0
  104. package/build/runtime/bootstrap.js +73 -0
  105. package/build/runtime/bootstrap.js.map +1 -0
  106. package/build/runtime/config.d.ts +152 -0
  107. package/build/runtime/config.d.ts.map +1 -0
  108. package/build/runtime/config.js +19 -0
  109. package/build/runtime/config.js.map +1 -0
  110. package/build/runtime/scanQrSeam.d.ts +7 -0
  111. package/build/runtime/scanQrSeam.d.ts.map +1 -0
  112. package/build/runtime/scanQrSeam.js +16 -0
  113. package/build/runtime/scanQrSeam.js.map +1 -0
  114. package/build/runtime/streamConfig.d.ts +4 -0
  115. package/build/runtime/streamConfig.d.ts.map +1 -0
  116. package/build/runtime/streamConfig.js +38 -0
  117. package/build/runtime/streamConfig.js.map +1 -0
  118. package/build/services/AiTierResolution.d.ts +61 -0
  119. package/build/services/AiTierResolution.d.ts.map +1 -0
  120. package/build/services/AiTierResolution.js +83 -0
  121. package/build/services/AiTierResolution.js.map +1 -0
  122. package/build/services/AppRegistry.d.ts +303 -0
  123. package/build/services/AppRegistry.d.ts.map +1 -0
  124. package/build/services/AppRegistry.js +1108 -0
  125. package/build/services/AppRegistry.js.map +1 -0
  126. package/build/services/AudioCloudUplink.d.ts +15 -0
  127. package/build/services/AudioCloudUplink.d.ts.map +1 -0
  128. package/build/services/AudioCloudUplink.js +54 -0
  129. package/build/services/AudioCloudUplink.js.map +1 -0
  130. package/build/services/AudioPlaybackService.d.ts +183 -0
  131. package/build/services/AudioPlaybackService.d.ts.map +1 -0
  132. package/build/services/AudioPlaybackService.js +750 -0
  133. package/build/services/AudioPlaybackService.js.map +1 -0
  134. package/build/services/AutomaticReportResult.d.ts +20 -0
  135. package/build/services/AutomaticReportResult.d.ts.map +1 -0
  136. package/build/services/AutomaticReportResult.js +29 -0
  137. package/build/services/AutomaticReportResult.js.map +1 -0
  138. package/build/services/BlobStore.d.ts +106 -0
  139. package/build/services/BlobStore.d.ts.map +1 -0
  140. package/build/services/BlobStore.js +741 -0
  141. package/build/services/BlobStore.js.map +1 -0
  142. package/build/services/CaptionsTesterReportService.d.ts +4 -0
  143. package/build/services/CaptionsTesterReportService.d.ts.map +1 -0
  144. package/build/services/CaptionsTesterReportService.js +80 -0
  145. package/build/services/CaptionsTesterReportService.js.map +1 -0
  146. package/build/services/CloudAudioSubscriptionSync.d.ts +24 -0
  147. package/build/services/CloudAudioSubscriptionSync.d.ts.map +1 -0
  148. package/build/services/CloudAudioSubscriptionSync.js +51 -0
  149. package/build/services/CloudAudioSubscriptionSync.js.map +1 -0
  150. package/build/services/CloudClientService.d.ts +293 -0
  151. package/build/services/CloudClientService.d.ts.map +1 -0
  152. package/build/services/CloudClientService.js +965 -0
  153. package/build/services/CloudClientService.js.map +1 -0
  154. package/build/services/CloudTranscriptE2EMetrics.d.ts +4 -0
  155. package/build/services/CloudTranscriptE2EMetrics.d.ts.map +1 -0
  156. package/build/services/CloudTranscriptE2EMetrics.js +24 -0
  157. package/build/services/CloudTranscriptE2EMetrics.js.map +1 -0
  158. package/build/services/ConnectionCoordinator.d.ts +51 -0
  159. package/build/services/ConnectionCoordinator.d.ts.map +1 -0
  160. package/build/services/ConnectionCoordinator.js +34 -0
  161. package/build/services/ConnectionCoordinator.js.map +1 -0
  162. package/build/services/DevServerBridge.d.ts +63 -0
  163. package/build/services/DevServerBridge.d.ts.map +1 -0
  164. package/build/services/DevServerBridge.js +296 -0
  165. package/build/services/DevServerBridge.js.map +1 -0
  166. package/build/services/DeviceEventRouter.d.ts +3 -0
  167. package/build/services/DeviceEventRouter.d.ts.map +1 -0
  168. package/build/services/DeviceEventRouter.js +214 -0
  169. package/build/services/DeviceEventRouter.js.map +1 -0
  170. package/build/services/DeviceStoreHydration.d.ts +38 -0
  171. package/build/services/DeviceStoreHydration.d.ts.map +1 -0
  172. package/build/services/DeviceStoreHydration.js +124 -0
  173. package/build/services/DeviceStoreHydration.js.map +1 -0
  174. package/build/services/DisplayProcessor.d.ts +223 -0
  175. package/build/services/DisplayProcessor.d.ts.map +1 -0
  176. package/build/services/DisplayProcessor.js +552 -0
  177. package/build/services/DisplayProcessor.js.map +1 -0
  178. package/build/services/ForegroundLocationPermission.d.ts +13 -0
  179. package/build/services/ForegroundLocationPermission.d.ts.map +1 -0
  180. package/build/services/ForegroundLocationPermission.js +12 -0
  181. package/build/services/ForegroundLocationPermission.js.map +1 -0
  182. package/build/services/GlassesReadiness.d.ts +41 -0
  183. package/build/services/GlassesReadiness.d.ts.map +1 -0
  184. package/build/services/GlassesReadiness.js +82 -0
  185. package/build/services/GlassesReadiness.js.map +1 -0
  186. package/build/services/GlassesSettingsSync.d.ts +33 -0
  187. package/build/services/GlassesSettingsSync.d.ts.map +1 -0
  188. package/build/services/GlassesSettingsSync.js +127 -0
  189. package/build/services/GlassesSettingsSync.js.map +1 -0
  190. package/build/services/GlassesStatusProjection.d.ts +3 -0
  191. package/build/services/GlassesStatusProjection.d.ts.map +1 -0
  192. package/build/services/GlassesStatusProjection.js +65 -0
  193. package/build/services/GlassesStatusProjection.js.map +1 -0
  194. package/build/services/HeadingService.d.ts +33 -0
  195. package/build/services/HeadingService.d.ts.map +1 -0
  196. package/build/services/HeadingService.js +123 -0
  197. package/build/services/HeadingService.js.map +1 -0
  198. package/build/services/HotspotOtaTransport.d.ts +29 -0
  199. package/build/services/HotspotOtaTransport.d.ts.map +1 -0
  200. package/build/services/HotspotOtaTransport.js +146 -0
  201. package/build/services/HotspotOtaTransport.js.map +1 -0
  202. package/build/services/HotspotShutdown.d.ts +17 -0
  203. package/build/services/HotspotShutdown.d.ts.map +1 -0
  204. package/build/services/HotspotShutdown.js +29 -0
  205. package/build/services/HotspotShutdown.js.map +1 -0
  206. package/build/services/JSCrashController.d.ts +97 -0
  207. package/build/services/JSCrashController.d.ts.map +1 -0
  208. package/build/services/JSCrashController.js +138 -0
  209. package/build/services/JSCrashController.js.map +1 -0
  210. package/build/services/JSCrashloopReportService.d.ts +9 -0
  211. package/build/services/JSCrashloopReportService.d.ts.map +1 -0
  212. package/build/services/JSCrashloopReportService.js +54 -0
  213. package/build/services/JSCrashloopReportService.js.map +1 -0
  214. package/build/services/JSLogPipeline.d.ts +87 -0
  215. package/build/services/JSLogPipeline.d.ts.map +1 -0
  216. package/build/services/JSLogPipeline.js +137 -0
  217. package/build/services/JSLogPipeline.js.map +1 -0
  218. package/build/services/JSRouter.d.ts +203 -0
  219. package/build/services/JSRouter.d.ts.map +1 -0
  220. package/build/services/JSRouter.js +482 -0
  221. package/build/services/JSRouter.js.map +1 -0
  222. package/build/services/LocalDisplayManager.d.ts +162 -0
  223. package/build/services/LocalDisplayManager.d.ts.map +1 -0
  224. package/build/services/LocalDisplayManager.js +687 -0
  225. package/build/services/LocalDisplayManager.js.map +1 -0
  226. package/build/services/LocalMiniappDataMigration.d.ts +46 -0
  227. package/build/services/LocalMiniappDataMigration.d.ts.map +1 -0
  228. package/build/services/LocalMiniappDataMigration.js +78 -0
  229. package/build/services/LocalMiniappDataMigration.js.map +1 -0
  230. package/build/services/LocalMiniappRuntime.d.ts +677 -0
  231. package/build/services/LocalMiniappRuntime.d.ts.map +1 -0
  232. package/build/services/LocalMiniappRuntime.js +5068 -0
  233. package/build/services/LocalMiniappRuntime.js.map +1 -0
  234. package/build/services/LocalMiniappStorage.d.ts +32 -0
  235. package/build/services/LocalMiniappStorage.d.ts.map +1 -0
  236. package/build/services/LocalMiniappStorage.js +66 -0
  237. package/build/services/LocalMiniappStorage.js.map +1 -0
  238. package/build/services/LocalMiniappUserIdentity.d.ts +41 -0
  239. package/build/services/LocalMiniappUserIdentity.d.ts.map +1 -0
  240. package/build/services/LocalMiniappUserIdentity.js +73 -0
  241. package/build/services/LocalMiniappUserIdentity.js.map +1 -0
  242. package/build/services/LocalSttFallbackCoordinator.d.ts +101 -0
  243. package/build/services/LocalSttFallbackCoordinator.d.ts.map +1 -0
  244. package/build/services/LocalSttFallbackCoordinator.js +210 -0
  245. package/build/services/LocalSttFallbackCoordinator.js.map +1 -0
  246. package/build/services/MapboxTokenService.d.ts +33 -0
  247. package/build/services/MapboxTokenService.d.ts.map +1 -0
  248. package/build/services/MapboxTokenService.js +115 -0
  249. package/build/services/MapboxTokenService.js.map +1 -0
  250. package/build/services/MemoryCapture.d.ts +40 -0
  251. package/build/services/MemoryCapture.d.ts.map +1 -0
  252. package/build/services/MemoryCapture.js +42 -0
  253. package/build/services/MemoryCapture.js.map +1 -0
  254. package/build/services/MemoryStore.d.ts +184 -0
  255. package/build/services/MemoryStore.d.ts.map +1 -0
  256. package/build/services/MemoryStore.js +541 -0
  257. package/build/services/MemoryStore.js.map +1 -0
  258. package/build/services/MicStateCoordinator.d.ts +78 -0
  259. package/build/services/MicStateCoordinator.d.ts.map +1 -0
  260. package/build/services/MicStateCoordinator.js +219 -0
  261. package/build/services/MicStateCoordinator.js.map +1 -0
  262. package/build/services/MiniappEngine.d.ts +31 -0
  263. package/build/services/MiniappEngine.d.ts.map +1 -0
  264. package/build/services/MiniappEngine.js +119 -0
  265. package/build/services/MiniappEngine.js.map +1 -0
  266. package/build/services/MiniappLauncher.d.ts +127 -0
  267. package/build/services/MiniappLauncher.d.ts.map +1 -0
  268. package/build/services/MiniappLauncher.js +309 -0
  269. package/build/services/MiniappLauncher.js.map +1 -0
  270. package/build/services/MiniappLiveness.d.ts +12 -0
  271. package/build/services/MiniappLiveness.d.ts.map +1 -0
  272. package/build/services/MiniappLiveness.js +16 -0
  273. package/build/services/MiniappLiveness.js.map +1 -0
  274. package/build/services/MiniappRunningRegistry.d.ts +24 -0
  275. package/build/services/MiniappRunningRegistry.d.ts.map +1 -0
  276. package/build/services/MiniappRunningRegistry.js +52 -0
  277. package/build/services/MiniappRunningRegistry.js.map +1 -0
  278. package/build/services/NativeHttpResponse.d.ts +3 -0
  279. package/build/services/NativeHttpResponse.d.ts.map +1 -0
  280. package/build/services/NativeHttpResponse.js +6 -0
  281. package/build/services/NativeHttpResponse.js.map +1 -0
  282. package/build/services/NativeNavigationService.d.ts +51 -0
  283. package/build/services/NativeNavigationService.d.ts.map +1 -0
  284. package/build/services/NativeNavigationService.js +154 -0
  285. package/build/services/NativeNavigationService.js.map +1 -0
  286. package/build/services/NavigationHandlers.d.ts +107 -0
  287. package/build/services/NavigationHandlers.d.ts.map +1 -0
  288. package/build/services/NavigationHandlers.js +479 -0
  289. package/build/services/NavigationHandlers.js.map +1 -0
  290. package/build/services/NavigationService.d.ts +283 -0
  291. package/build/services/NavigationService.d.ts.map +1 -0
  292. package/build/services/NavigationService.js +696 -0
  293. package/build/services/NavigationService.js.map +1 -0
  294. package/build/services/NotificationsEmitter.d.ts +28 -0
  295. package/build/services/NotificationsEmitter.d.ts.map +1 -0
  296. package/build/services/NotificationsEmitter.js +22 -0
  297. package/build/services/NotificationsEmitter.js.map +1 -0
  298. package/build/services/OfflineSpeechModelService.d.ts +33 -0
  299. package/build/services/OfflineSpeechModelService.d.ts.map +1 -0
  300. package/build/services/OfflineSpeechModelService.js +136 -0
  301. package/build/services/OfflineSpeechModelService.js.map +1 -0
  302. package/build/services/OtaArtifactDownloader.d.ts +65 -0
  303. package/build/services/OtaArtifactDownloader.d.ts.map +1 -0
  304. package/build/services/OtaArtifactDownloader.js +226 -0
  305. package/build/services/OtaArtifactDownloader.js.map +1 -0
  306. package/build/services/OtaInstallCoordinator.d.ts +275 -0
  307. package/build/services/OtaInstallCoordinator.d.ts.map +1 -0
  308. package/build/services/OtaInstallCoordinator.js +1491 -0
  309. package/build/services/OtaInstallCoordinator.js.map +1 -0
  310. package/build/services/OtaService.d.ts +3 -0
  311. package/build/services/OtaService.d.ts.map +1 -0
  312. package/build/services/OtaService.js +72 -0
  313. package/build/services/OtaService.js.map +1 -0
  314. package/build/services/OtaUpdateCheckService.d.ts +100 -0
  315. package/build/services/OtaUpdateCheckService.d.ts.map +1 -0
  316. package/build/services/OtaUpdateCheckService.js +380 -0
  317. package/build/services/OtaUpdateCheckService.js.map +1 -0
  318. package/build/services/PairingIdentity.d.ts +80 -0
  319. package/build/services/PairingIdentity.d.ts.map +1 -0
  320. package/build/services/PairingIdentity.js +145 -0
  321. package/build/services/PairingIdentity.js.map +1 -0
  322. package/build/services/PhoneCalendarHelpers.d.ts +32 -0
  323. package/build/services/PhoneCalendarHelpers.d.ts.map +1 -0
  324. package/build/services/PhoneCalendarHelpers.js +157 -0
  325. package/build/services/PhoneCalendarHelpers.js.map +1 -0
  326. package/build/services/PhoneCalendarService.d.ts +5 -0
  327. package/build/services/PhoneCalendarService.d.ts.map +1 -0
  328. package/build/services/PhoneCalendarService.js +45 -0
  329. package/build/services/PhoneCalendarService.js.map +1 -0
  330. package/build/services/PhoneCameraFovCoordinator.d.ts +30 -0
  331. package/build/services/PhoneCameraFovCoordinator.d.ts.map +1 -0
  332. package/build/services/PhoneCameraFovCoordinator.js +194 -0
  333. package/build/services/PhoneCameraFovCoordinator.js.map +1 -0
  334. package/build/services/PhoneLocationService.d.ts +36 -0
  335. package/build/services/PhoneLocationService.d.ts.map +1 -0
  336. package/build/services/PhoneLocationService.js +115 -0
  337. package/build/services/PhoneLocationService.js.map +1 -0
  338. package/build/services/PhoneNotificationsSync.d.ts +3 -0
  339. package/build/services/PhoneNotificationsSync.d.ts.map +1 -0
  340. package/build/services/PhoneNotificationsSync.js +37 -0
  341. package/build/services/PhoneNotificationsSync.js.map +1 -0
  342. package/build/services/PhonePhotoCoordinator.d.ts +110 -0
  343. package/build/services/PhonePhotoCoordinator.d.ts.map +1 -0
  344. package/build/services/PhonePhotoCoordinator.js +344 -0
  345. package/build/services/PhonePhotoCoordinator.js.map +1 -0
  346. package/build/services/PhoneStreamCoordinator.d.ts +175 -0
  347. package/build/services/PhoneStreamCoordinator.d.ts.map +1 -0
  348. package/build/services/PhoneStreamCoordinator.js +740 -0
  349. package/build/services/PhoneStreamCoordinator.js.map +1 -0
  350. package/build/services/PhoneVideoCoordinator.d.ts +52 -0
  351. package/build/services/PhoneVideoCoordinator.d.ts.map +1 -0
  352. package/build/services/PhoneVideoCoordinator.js +157 -0
  353. package/build/services/PhoneVideoCoordinator.js.map +1 -0
  354. package/build/services/ProviderKeyValidation.d.ts +27 -0
  355. package/build/services/ProviderKeyValidation.d.ts.map +1 -0
  356. package/build/services/ProviderKeyValidation.js +42 -0
  357. package/build/services/ProviderKeyValidation.js.map +1 -0
  358. package/build/services/STTModelManager.d.ts +65 -0
  359. package/build/services/STTModelManager.d.ts.map +1 -0
  360. package/build/services/STTModelManager.js +366 -0
  361. package/build/services/STTModelManager.js.map +1 -0
  362. package/build/services/SceneRenderer.d.ts +90 -0
  363. package/build/services/SceneRenderer.d.ts.map +1 -0
  364. package/build/services/SceneRenderer.js +274 -0
  365. package/build/services/SceneRenderer.js.map +1 -0
  366. package/build/services/SentenceTtsPipeline.d.ts +31 -0
  367. package/build/services/SentenceTtsPipeline.d.ts.map +1 -0
  368. package/build/services/SentenceTtsPipeline.js +67 -0
  369. package/build/services/SentenceTtsPipeline.js.map +1 -0
  370. package/build/services/SpeakerIdModelManager.d.ts +36 -0
  371. package/build/services/SpeakerIdModelManager.d.ts.map +1 -0
  372. package/build/services/SpeakerIdModelManager.js +132 -0
  373. package/build/services/SpeakerIdModelManager.js.map +1 -0
  374. package/build/services/SpeakerIdSync.d.ts +14 -0
  375. package/build/services/SpeakerIdSync.d.ts.map +1 -0
  376. package/build/services/SpeakerIdSync.js +62 -0
  377. package/build/services/SpeakerIdSync.js.map +1 -0
  378. package/build/services/SpeakerLabelResolver.d.ts +80 -0
  379. package/build/services/SpeakerLabelResolver.d.ts.map +1 -0
  380. package/build/services/SpeakerLabelResolver.js +108 -0
  381. package/build/services/SpeakerLabelResolver.js.map +1 -0
  382. package/build/services/SpeakerProfileStore.d.ts +90 -0
  383. package/build/services/SpeakerProfileStore.d.ts.map +1 -0
  384. package/build/services/SpeakerProfileStore.js +213 -0
  385. package/build/services/SpeakerProfileStore.js.map +1 -0
  386. package/build/services/SpeakerSegmentResolver.d.ts +87 -0
  387. package/build/services/SpeakerSegmentResolver.d.ts.map +1 -0
  388. package/build/services/SpeakerSegmentResolver.js +137 -0
  389. package/build/services/SpeakerSegmentResolver.js.map +1 -0
  390. package/build/services/StreamLifecycleController.d.ts +63 -0
  391. package/build/services/StreamLifecycleController.d.ts.map +1 -0
  392. package/build/services/StreamLifecycleController.js +148 -0
  393. package/build/services/StreamLifecycleController.js.map +1 -0
  394. package/build/services/SupportProfileSync.d.ts +22 -0
  395. package/build/services/SupportProfileSync.d.ts.map +1 -0
  396. package/build/services/SupportProfileSync.js +213 -0
  397. package/build/services/SupportProfileSync.js.map +1 -0
  398. package/build/services/TTSModelManager.d.ts +90 -0
  399. package/build/services/TTSModelManager.d.ts.map +1 -0
  400. package/build/services/TTSModelManager.js +366 -0
  401. package/build/services/TTSModelManager.js.map +1 -0
  402. package/build/services/TranscriptionRouting.d.ts +22 -0
  403. package/build/services/TranscriptionRouting.d.ts.map +1 -0
  404. package/build/services/TranscriptionRouting.js +34 -0
  405. package/build/services/TranscriptionRouting.js.map +1 -0
  406. package/build/services/TtsTextSanitizer.d.ts +5 -0
  407. package/build/services/TtsTextSanitizer.d.ts.map +1 -0
  408. package/build/services/TtsTextSanitizer.js +61 -0
  409. package/build/services/TtsTextSanitizer.js.map +1 -0
  410. package/build/services/UIRouter.d.ts +137 -0
  411. package/build/services/UIRouter.d.ts.map +1 -0
  412. package/build/services/UIRouter.js +210 -0
  413. package/build/services/UIRouter.js.map +1 -0
  414. package/build/services/WebviewBridge.d.ts +22 -0
  415. package/build/services/WebviewBridge.d.ts.map +1 -0
  416. package/build/services/WebviewBridge.js +47 -0
  417. package/build/services/WebviewBridge.js.map +1 -0
  418. package/build/services/WifiAdbRequest.d.ts +17 -0
  419. package/build/services/WifiAdbRequest.d.ts.map +1 -0
  420. package/build/services/WifiAdbRequest.js +35 -0
  421. package/build/services/WifiAdbRequest.js.map +1 -0
  422. package/build/services/asg/GalleryMediaIntegrityReportService.d.ts +17 -0
  423. package/build/services/asg/GalleryMediaIntegrityReportService.d.ts.map +1 -0
  424. package/build/services/asg/GalleryMediaIntegrityReportService.js +43 -0
  425. package/build/services/asg/GalleryMediaIntegrityReportService.js.map +1 -0
  426. package/build/services/asg/asgCameraApi.d.ts +208 -0
  427. package/build/services/asg/asgCameraApi.d.ts.map +1 -0
  428. package/build/services/asg/asgCameraApi.js +1480 -0
  429. package/build/services/asg/asgCameraApi.js.map +1 -0
  430. package/build/services/asg/cameraRollExportCoordinator.d.ts +60 -0
  431. package/build/services/asg/cameraRollExportCoordinator.d.ts.map +1 -0
  432. package/build/services/asg/cameraRollExportCoordinator.js +618 -0
  433. package/build/services/asg/cameraRollExportCoordinator.js.map +1 -0
  434. package/build/services/asg/cameraRollExportLedger.d.ts +44 -0
  435. package/build/services/asg/cameraRollExportLedger.d.ts.map +1 -0
  436. package/build/services/asg/cameraRollExportLedger.js +78 -0
  437. package/build/services/asg/cameraRollExportLedger.js.map +1 -0
  438. package/build/services/asg/galleryMediaValidation.d.ts +29 -0
  439. package/build/services/asg/galleryMediaValidation.d.ts.map +1 -0
  440. package/build/services/asg/galleryMediaValidation.js +149 -0
  441. package/build/services/asg/galleryMediaValidation.js.map +1 -0
  442. package/build/services/asg/galleryNotices.d.ts +33 -0
  443. package/build/services/asg/galleryNotices.d.ts.map +1 -0
  444. package/build/services/asg/galleryNotices.js +29 -0
  445. package/build/services/asg/galleryNotices.js.map +1 -0
  446. package/build/services/asg/galleryPathMigration.d.ts +3 -0
  447. package/build/services/asg/galleryPathMigration.d.ts.map +1 -0
  448. package/build/services/asg/galleryPathMigration.js +26 -0
  449. package/build/services/asg/galleryPathMigration.js.map +1 -0
  450. package/build/services/asg/gallerySettingsService.d.ts +20 -0
  451. package/build/services/asg/gallerySettingsService.d.ts.map +1 -0
  452. package/build/services/asg/gallerySettingsService.js +53 -0
  453. package/build/services/asg/gallerySettingsService.js.map +1 -0
  454. package/build/services/asg/gallerySyncNotifications.d.ts +56 -0
  455. package/build/services/asg/gallerySyncNotifications.d.ts.map +1 -0
  456. package/build/services/asg/gallerySyncNotifications.js +198 -0
  457. package/build/services/asg/gallerySyncNotifications.js.map +1 -0
  458. package/build/services/asg/gallerySyncService.d.ts +137 -0
  459. package/build/services/asg/gallerySyncService.d.ts.map +1 -0
  460. package/build/services/asg/gallerySyncService.js +1945 -0
  461. package/build/services/asg/gallerySyncService.js.map +1 -0
  462. package/build/services/asg/galleryTransferLedger.d.ts +58 -0
  463. package/build/services/asg/galleryTransferLedger.d.ts.map +1 -0
  464. package/build/services/asg/galleryTransferLedger.js +225 -0
  465. package/build/services/asg/galleryTransferLedger.js.map +1 -0
  466. package/build/services/asg/localNetworkTransport.d.ts +15 -0
  467. package/build/services/asg/localNetworkTransport.d.ts.map +1 -0
  468. package/build/services/asg/localNetworkTransport.js +144 -0
  469. package/build/services/asg/localNetworkTransport.js.map +1 -0
  470. package/build/services/asg/localStorageService.d.ts +146 -0
  471. package/build/services/asg/localStorageService.d.ts.map +1 -0
  472. package/build/services/asg/localStorageService.js +572 -0
  473. package/build/services/asg/localStorageService.js.map +1 -0
  474. package/build/services/asg/mediaProcessingQueue.d.ts +70 -0
  475. package/build/services/asg/mediaProcessingQueue.d.ts.map +1 -0
  476. package/build/services/asg/mediaProcessingQueue.js +451 -0
  477. package/build/services/asg/mediaProcessingQueue.js.map +1 -0
  478. package/build/services/audioPlaybackAssets.d.ts +8 -0
  479. package/build/services/audioPlaybackAssets.d.ts.map +1 -0
  480. package/build/services/audioPlaybackAssets.js +7 -0
  481. package/build/services/audioPlaybackAssets.js.map +1 -0
  482. package/build/services/blobPaths.d.ts +31 -0
  483. package/build/services/blobPaths.d.ts.map +1 -0
  484. package/build/services/blobPaths.js +86 -0
  485. package/build/services/blobPaths.js.map +1 -0
  486. package/build/services/cloudStreamApi.d.ts +59 -0
  487. package/build/services/cloudStreamApi.d.ts.map +1 -0
  488. package/build/services/cloudStreamApi.js +81 -0
  489. package/build/services/cloudStreamApi.js.map +1 -0
  490. package/build/services/effectiveCapabilities.d.ts +8 -0
  491. package/build/services/effectiveCapabilities.d.ts.map +1 -0
  492. package/build/services/effectiveCapabilities.js +23 -0
  493. package/build/services/effectiveCapabilities.js.map +1 -0
  494. package/build/services/gallerySyncClock.d.ts +17 -0
  495. package/build/services/gallerySyncClock.d.ts.map +1 -0
  496. package/build/services/gallerySyncClock.js +21 -0
  497. package/build/services/gallerySyncClock.js.map +1 -0
  498. package/build/services/glassesClockSync.d.ts +20 -0
  499. package/build/services/glassesClockSync.d.ts.map +1 -0
  500. package/build/services/glassesClockSync.js +97 -0
  501. package/build/services/glassesClockSync.js.map +1 -0
  502. package/build/services/manifestActions.d.ts +31 -0
  503. package/build/services/manifestActions.d.ts.map +1 -0
  504. package/build/services/manifestActions.js +54 -0
  505. package/build/services/manifestActions.js.map +1 -0
  506. package/build/services/manifestVersionGate.d.ts +28 -0
  507. package/build/services/manifestVersionGate.d.ts.map +1 -0
  508. package/build/services/manifestVersionGate.js +41 -0
  509. package/build/services/manifestVersionGate.js.map +1 -0
  510. package/build/services/memoryStoreInstance.d.ts +19 -0
  511. package/build/services/memoryStoreInstance.d.ts.map +1 -0
  512. package/build/services/memoryStoreInstance.js +65 -0
  513. package/build/services/memoryStoreInstance.js.map +1 -0
  514. package/build/services/navigation/roadNameResolver.d.ts +67 -0
  515. package/build/services/navigation/roadNameResolver.d.ts.map +1 -0
  516. package/build/services/navigation/roadNameResolver.js +87 -0
  517. package/build/services/navigation/roadNameResolver.js.map +1 -0
  518. package/build/services/navigation/routesApiCodec.d.ts +38 -0
  519. package/build/services/navigation/routesApiCodec.d.ts.map +1 -0
  520. package/build/services/navigation/routesApiCodec.js +68 -0
  521. package/build/services/navigation/routesApiCodec.js.map +1 -0
  522. package/build/services/otaDisplayState.d.ts +53 -0
  523. package/build/services/otaDisplayState.d.ts.map +1 -0
  524. package/build/services/otaDisplayState.js +114 -0
  525. package/build/services/otaDisplayState.js.map +1 -0
  526. package/build/services/otaInstallPolicy.d.ts +93 -0
  527. package/build/services/otaInstallPolicy.d.ts.map +1 -0
  528. package/build/services/otaInstallPolicy.js +117 -0
  529. package/build/services/otaInstallPolicy.js.map +1 -0
  530. package/build/services/otaLegacyMapping.d.ts +22 -0
  531. package/build/services/otaLegacyMapping.d.ts.map +1 -0
  532. package/build/services/otaLegacyMapping.js +56 -0
  533. package/build/services/otaLegacyMapping.js.map +1 -0
  534. package/build/services/otaManifestUrl.d.ts +2 -0
  535. package/build/services/otaManifestUrl.d.ts.map +1 -0
  536. package/build/services/otaManifestUrl.js +46 -0
  537. package/build/services/otaManifestUrl.js.map +1 -0
  538. package/build/services/slimStreamStatus.d.ts +18 -0
  539. package/build/services/slimStreamStatus.d.ts.map +1 -0
  540. package/build/services/slimStreamStatus.js +36 -0
  541. package/build/services/slimStreamStatus.js.map +1 -0
  542. package/build/services/soniox/SonioxDirectService.d.ts +109 -0
  543. package/build/services/soniox/SonioxDirectService.d.ts.map +1 -0
  544. package/build/services/soniox/SonioxDirectService.js +449 -0
  545. package/build/services/soniox/SonioxDirectService.js.map +1 -0
  546. package/build/services/soniox/SonioxRealtimeClient.d.ts +121 -0
  547. package/build/services/soniox/SonioxRealtimeClient.d.ts.map +1 -0
  548. package/build/services/soniox/SonioxRealtimeClient.js +301 -0
  549. package/build/services/soniox/SonioxRealtimeClient.js.map +1 -0
  550. package/build/services/soniox/SonioxTokenAssembler.d.ts +98 -0
  551. package/build/services/soniox/SonioxTokenAssembler.d.ts.map +1 -0
  552. package/build/services/soniox/SonioxTokenAssembler.js +394 -0
  553. package/build/services/soniox/SonioxTokenAssembler.js.map +1 -0
  554. package/build/services/speakerProfileStoreInstance.d.ts +8 -0
  555. package/build/services/speakerProfileStoreInstance.d.ts.map +1 -0
  556. package/build/services/speakerProfileStoreInstance.js +24 -0
  557. package/build/services/speakerProfileStoreInstance.js.map +1 -0
  558. package/build/services/uiShim.d.ts +38 -0
  559. package/build/services/uiShim.d.ts.map +1 -0
  560. package/build/services/uiShim.js +369 -0
  561. package/build/services/uiShim.js.map +1 -0
  562. package/build/stores/apps.d.ts +104 -0
  563. package/build/stores/apps.d.ts.map +1 -0
  564. package/build/stores/apps.js +503 -0
  565. package/build/stores/apps.js.map +1 -0
  566. package/build/stores/bluetoothSettingKeys.d.ts +4 -0
  567. package/build/stores/bluetoothSettingKeys.d.ts.map +1 -0
  568. package/build/stores/bluetoothSettingKeys.js +33 -0
  569. package/build/stores/bluetoothSettingKeys.js.map +1 -0
  570. package/build/stores/cloudClientStatus.d.ts +9 -0
  571. package/build/stores/cloudClientStatus.d.ts.map +1 -0
  572. package/build/stores/cloudClientStatus.js +24 -0
  573. package/build/stores/cloudClientStatus.js.map +1 -0
  574. package/build/stores/connection.d.ts +22 -0
  575. package/build/stores/connection.d.ts.map +1 -0
  576. package/build/stores/connection.js +43 -0
  577. package/build/stores/connection.js.map +1 -0
  578. package/build/stores/core.d.ts +15 -0
  579. package/build/stores/core.d.ts.map +1 -0
  580. package/build/stores/core.js +21 -0
  581. package/build/stores/core.js.map +1 -0
  582. package/build/stores/display.d.ts +24 -0
  583. package/build/stores/display.d.ts.map +1 -0
  584. package/build/stores/display.js +96 -0
  585. package/build/stores/display.js.map +1 -0
  586. package/build/stores/gallerySync.d.ts +103 -0
  587. package/build/stores/gallerySync.d.ts.map +1 -0
  588. package/build/stores/gallerySync.js +219 -0
  589. package/build/stores/gallerySync.js.map +1 -0
  590. package/build/stores/glasses.d.ts +62 -0
  591. package/build/stores/glasses.d.ts.map +1 -0
  592. package/build/stores/glasses.js +208 -0
  593. package/build/stores/glasses.js.map +1 -0
  594. package/build/stores/settings.d.ts +54 -0
  595. package/build/stores/settings.d.ts.map +1 -0
  596. package/build/stores/settings.js +1200 -0
  597. package/build/stores/settings.js.map +1 -0
  598. package/build/stores/transcriptionDebug.d.ts +25 -0
  599. package/build/stores/transcriptionDebug.d.ts.map +1 -0
  600. package/build/stores/transcriptionDebug.js +0 -0
  601. package/build/stores/transcriptionDebug.js.map +1 -0
  602. package/build/types/applet.d.ts +101 -0
  603. package/build/types/applet.d.ts.map +1 -0
  604. package/build/types/applet.js +5 -0
  605. package/build/types/applet.js.map +1 -0
  606. package/build/types/asg.d.ts +53 -0
  607. package/build/types/asg.d.ts.map +1 -0
  608. package/build/types/asg.js +2 -0
  609. package/build/types/asg.js.map +1 -0
  610. package/build/types/capabilities/even-realities-g1.d.ts +12 -0
  611. package/build/types/capabilities/even-realities-g1.d.ts.map +1 -0
  612. package/build/types/capabilities/even-realities-g1.js +57 -0
  613. package/build/types/capabilities/even-realities-g1.js.map +1 -0
  614. package/build/types/capabilities/even-realities-g2.d.ts +12 -0
  615. package/build/types/capabilities/even-realities-g2.d.ts.map +1 -0
  616. package/build/types/capabilities/even-realities-g2.js +83 -0
  617. package/build/types/capabilities/even-realities-g2.js.map +1 -0
  618. package/build/types/capabilities/mentra-display.d.ts +12 -0
  619. package/build/types/capabilities/mentra-display.d.ts.map +1 -0
  620. package/build/types/capabilities/mentra-display.js +65 -0
  621. package/build/types/capabilities/mentra-display.js.map +1 -0
  622. package/build/types/capabilities/mentra-live.d.ts +12 -0
  623. package/build/types/capabilities/mentra-live.d.ts.map +1 -0
  624. package/build/types/capabilities/mentra-live.js +94 -0
  625. package/build/types/capabilities/mentra-live.js.map +1 -0
  626. package/build/types/capabilities/nimo.d.ts +13 -0
  627. package/build/types/capabilities/nimo.d.ts.map +1 -0
  628. package/build/types/capabilities/nimo.js +72 -0
  629. package/build/types/capabilities/nimo.js.map +1 -0
  630. package/build/types/capabilities/none.d.ts +13 -0
  631. package/build/types/capabilities/none.d.ts.map +1 -0
  632. package/build/types/capabilities/none.js +67 -0
  633. package/build/types/capabilities/none.js.map +1 -0
  634. package/build/types/capabilities/simulated-glasses.d.ts +13 -0
  635. package/build/types/capabilities/simulated-glasses.d.ts.map +1 -0
  636. package/build/types/capabilities/simulated-glasses.js +82 -0
  637. package/build/types/capabilities/simulated-glasses.js.map +1 -0
  638. package/build/types/capabilities/vuzix-z100.d.ts +12 -0
  639. package/build/types/capabilities/vuzix-z100.d.ts.map +1 -0
  640. package/build/types/capabilities/vuzix-z100.js +54 -0
  641. package/build/types/capabilities/vuzix-z100.js.map +1 -0
  642. package/build/types/enums.d.ts +22 -0
  643. package/build/types/enums.d.ts.map +1 -0
  644. package/build/types/enums.js +27 -0
  645. package/build/types/enums.js.map +1 -0
  646. package/build/types/hardware.d.ts +155 -0
  647. package/build/types/hardware.d.ts.map +1 -0
  648. package/build/types/hardware.js +52 -0
  649. package/build/types/hardware.js.map +1 -0
  650. package/build/types/index.d.ts +11 -0
  651. package/build/types/index.d.ts.map +1 -0
  652. package/build/types/index.js +10 -0
  653. package/build/types/index.js.map +1 -0
  654. package/build/utils/GlobalEventEmitter.d.ts +11 -0
  655. package/build/utils/GlobalEventEmitter.d.ts.map +1 -0
  656. package/build/utils/GlobalEventEmitter.js +14 -0
  657. package/build/utils/GlobalEventEmitter.js.map +1 -0
  658. package/build/utils/cloudClient/RnUdpAdapter.d.ts +3 -0
  659. package/build/utils/cloudClient/RnUdpAdapter.d.ts.map +1 -0
  660. package/build/utils/cloudClient/RnUdpAdapter.js +41 -0
  661. package/build/utils/cloudClient/RnUdpAdapter.js.map +1 -0
  662. package/build/utils/cloudClient/cloudSecureStore.d.ts +3 -0
  663. package/build/utils/cloudClient/cloudSecureStore.d.ts.map +1 -0
  664. package/build/utils/cloudClient/cloudSecureStore.js +26 -0
  665. package/build/utils/cloudClient/cloudSecureStore.js.map +1 -0
  666. package/build/utils/configuredDevHost.d.ts +6 -0
  667. package/build/utils/configuredDevHost.d.ts.map +1 -0
  668. package/build/utils/configuredDevHost.js +19 -0
  669. package/build/utils/configuredDevHost.js.map +1 -0
  670. package/build/utils/debouncedPatch.d.ts +6 -0
  671. package/build/utils/debouncedPatch.d.ts.map +1 -0
  672. package/build/utils/debouncedPatch.js +25 -0
  673. package/build/utils/debouncedPatch.js.map +1 -0
  674. package/build/utils/devLogging.d.ts +26 -0
  675. package/build/utils/devLogging.d.ts.map +1 -0
  676. package/build/utils/devLogging.js +116 -0
  677. package/build/utils/devLogging.js.map +1 -0
  678. package/build/utils/devMiniappLaunch.d.ts +55 -0
  679. package/build/utils/devMiniappLaunch.d.ts.map +1 -0
  680. package/build/utils/devMiniappLaunch.js +175 -0
  681. package/build/utils/devMiniappLaunch.js.map +1 -0
  682. package/build/utils/diagnosticContext.d.ts +3 -0
  683. package/build/utils/diagnosticContext.d.ts.map +1 -0
  684. package/build/utils/diagnosticContext.js +122 -0
  685. package/build/utils/diagnosticContext.js.map +1 -0
  686. package/build/utils/display/composer/ColumnComposer.d.ts +131 -0
  687. package/build/utils/display/composer/ColumnComposer.d.ts.map +1 -0
  688. package/build/utils/display/composer/ColumnComposer.js +204 -0
  689. package/build/utils/display/composer/ColumnComposer.js.map +1 -0
  690. package/build/utils/display/composer/index.d.ts +9 -0
  691. package/build/utils/display/composer/index.d.ts.map +1 -0
  692. package/build/utils/display/composer/index.js +9 -0
  693. package/build/utils/display/composer/index.js.map +1 -0
  694. package/build/utils/display/helpers/DisplayHelpers.d.ts +165 -0
  695. package/build/utils/display/helpers/DisplayHelpers.d.ts.map +1 -0
  696. package/build/utils/display/helpers/DisplayHelpers.js +312 -0
  697. package/build/utils/display/helpers/DisplayHelpers.js.map +1 -0
  698. package/build/utils/display/helpers/ScrollView.d.ts +183 -0
  699. package/build/utils/display/helpers/ScrollView.d.ts.map +1 -0
  700. package/build/utils/display/helpers/ScrollView.js +257 -0
  701. package/build/utils/display/helpers/ScrollView.js.map +1 -0
  702. package/build/utils/display/helpers/index.d.ts +11 -0
  703. package/build/utils/display/helpers/index.d.ts.map +1 -0
  704. package/build/utils/display/helpers/index.js +10 -0
  705. package/build/utils/display/helpers/index.js.map +1 -0
  706. package/build/utils/display/index.d.ts +174 -0
  707. package/build/utils/display/index.d.ts.map +1 -0
  708. package/build/utils/display/index.js +188 -0
  709. package/build/utils/display/index.js.map +1 -0
  710. package/build/utils/display/measurer/TextMeasurer.d.ts +160 -0
  711. package/build/utils/display/measurer/TextMeasurer.d.ts.map +1 -0
  712. package/build/utils/display/measurer/TextMeasurer.js +273 -0
  713. package/build/utils/display/measurer/TextMeasurer.js.map +1 -0
  714. package/build/utils/display/measurer/index.d.ts +10 -0
  715. package/build/utils/display/measurer/index.d.ts.map +1 -0
  716. package/build/utils/display/measurer/index.js +11 -0
  717. package/build/utils/display/measurer/index.js.map +1 -0
  718. package/build/utils/display/measurer/script-detection.d.ts +53 -0
  719. package/build/utils/display/measurer/script-detection.d.ts.map +1 -0
  720. package/build/utils/display/measurer/script-detection.js +184 -0
  721. package/build/utils/display/measurer/script-detection.js.map +1 -0
  722. package/build/utils/display/profiles/g1.d.ts +33 -0
  723. package/build/utils/display/profiles/g1.d.ts.map +1 -0
  724. package/build/utils/display/profiles/g1.js +213 -0
  725. package/build/utils/display/profiles/g1.js.map +1 -0
  726. package/build/utils/display/profiles/g2.d.ts +13 -0
  727. package/build/utils/display/profiles/g2.d.ts.map +1 -0
  728. package/build/utils/display/profiles/g2.js +34 -0
  729. package/build/utils/display/profiles/g2.js.map +1 -0
  730. package/build/utils/display/profiles/index.d.ts +12 -0
  731. package/build/utils/display/profiles/index.d.ts.map +1 -0
  732. package/build/utils/display/profiles/index.js +15 -0
  733. package/build/utils/display/profiles/index.js.map +1 -0
  734. package/build/utils/display/profiles/nex.d.ts +10 -0
  735. package/build/utils/display/profiles/nex.d.ts.map +1 -0
  736. package/build/utils/display/profiles/nex.js +150 -0
  737. package/build/utils/display/profiles/nex.js.map +1 -0
  738. package/build/utils/display/profiles/types.d.ts +103 -0
  739. package/build/utils/display/profiles/types.d.ts.map +1 -0
  740. package/build/utils/display/profiles/types.js +2 -0
  741. package/build/utils/display/profiles/types.js.map +1 -0
  742. package/build/utils/display/profiles/z100.d.ts +33 -0
  743. package/build/utils/display/profiles/z100.d.ts.map +1 -0
  744. package/build/utils/display/profiles/z100.js +182 -0
  745. package/build/utils/display/profiles/z100.js.map +1 -0
  746. package/build/utils/display/scene/anchor.d.ts +17 -0
  747. package/build/utils/display/scene/anchor.d.ts.map +1 -0
  748. package/build/utils/display/scene/anchor.js +33 -0
  749. package/build/utils/display/scene/anchor.js.map +1 -0
  750. package/build/utils/display/scene/degrade.d.ts +25 -0
  751. package/build/utils/display/scene/degrade.d.ts.map +1 -0
  752. package/build/utils/display/scene/degrade.js +64 -0
  753. package/build/utils/display/scene/degrade.js.map +1 -0
  754. package/build/utils/display/scene/differ.d.ts +38 -0
  755. package/build/utils/display/scene/differ.d.ts.map +1 -0
  756. package/build/utils/display/scene/differ.js +107 -0
  757. package/build/utils/display/scene/differ.js.map +1 -0
  758. package/build/utils/display/scene/index.d.ts +11 -0
  759. package/build/utils/display/scene/index.d.ts.map +1 -0
  760. package/build/utils/display/scene/index.js +7 -0
  761. package/build/utils/display/scene/index.js.map +1 -0
  762. package/build/utils/display/scene/process.d.ts +35 -0
  763. package/build/utils/display/scene/process.d.ts.map +1 -0
  764. package/build/utils/display/scene/process.js +201 -0
  765. package/build/utils/display/scene/process.js.map +1 -0
  766. package/build/utils/display/scene/store.d.ts +48 -0
  767. package/build/utils/display/scene/store.d.ts.map +1 -0
  768. package/build/utils/display/scene/store.js +0 -0
  769. package/build/utils/display/scene/store.js.map +1 -0
  770. package/build/utils/display/scene/types.d.ts +139 -0
  771. package/build/utils/display/scene/types.d.ts.map +1 -0
  772. package/build/utils/display/scene/types.js +0 -0
  773. package/build/utils/display/scene/types.js.map +1 -0
  774. package/build/utils/display/wrapper/TextWrapper.d.ts +102 -0
  775. package/build/utils/display/wrapper/TextWrapper.d.ts.map +1 -0
  776. package/build/utils/display/wrapper/TextWrapper.js +471 -0
  777. package/build/utils/display/wrapper/TextWrapper.js.map +1 -0
  778. package/build/utils/display/wrapper/index.d.ts +12 -0
  779. package/build/utils/display/wrapper/index.d.ts.map +1 -0
  780. package/build/utils/display/wrapper/index.js +12 -0
  781. package/build/utils/display/wrapper/index.js.map +1 -0
  782. package/build/utils/display/wrapper/types.d.ts +75 -0
  783. package/build/utils/display/wrapper/types.d.ts.map +1 -0
  784. package/build/utils/display/wrapper/types.js +11 -0
  785. package/build/utils/display/wrapper/types.js.map +1 -0
  786. package/build/utils/e2eMetrics.d.ts +6 -0
  787. package/build/utils/e2eMetrics.d.ts.map +1 -0
  788. package/build/utils/e2eMetrics.js +27 -0
  789. package/build/utils/e2eMetrics.js.map +1 -0
  790. package/build/utils/hardware/hardware.d.ts +42 -0
  791. package/build/utils/hardware/hardware.d.ts.map +1 -0
  792. package/build/utils/hardware/hardware.js +131 -0
  793. package/build/utils/hardware/hardware.js.map +1 -0
  794. package/build/utils/hardware/index.d.ts +2 -0
  795. package/build/utils/hardware/index.d.ts.map +1 -0
  796. package/build/utils/hardware/index.js +2 -0
  797. package/build/utils/hardware/index.js.map +1 -0
  798. package/build/utils/memory/search.d.ts +72 -0
  799. package/build/utils/memory/search.d.ts.map +1 -0
  800. package/build/utils/memory/search.js +178 -0
  801. package/build/utils/memory/search.js.map +1 -0
  802. package/build/utils/miniappDiagnostics.d.ts +64 -0
  803. package/build/utils/miniappDiagnostics.d.ts.map +1 -0
  804. package/build/utils/miniappDiagnostics.js +173 -0
  805. package/build/utils/miniappDiagnostics.js.map +1 -0
  806. package/build/utils/miniappGlobals.d.ts +59 -0
  807. package/build/utils/miniappGlobals.d.ts.map +1 -0
  808. package/build/utils/miniappGlobals.js +223 -0
  809. package/build/utils/miniappGlobals.js.map +1 -0
  810. package/build/utils/permissions/MediaLibraryPermissions.d.ts +45 -0
  811. package/build/utils/permissions/MediaLibraryPermissions.d.ts.map +1 -0
  812. package/build/utils/permissions/MediaLibraryPermissions.js +145 -0
  813. package/build/utils/permissions/MediaLibraryPermissions.js.map +1 -0
  814. package/build/utils/permissions/galleryDisplayName.d.ts +8 -0
  815. package/build/utils/permissions/galleryDisplayName.d.ts.map +1 -0
  816. package/build/utils/permissions/galleryDisplayName.js +25 -0
  817. package/build/utils/permissions/galleryDisplayName.js.map +1 -0
  818. package/build/utils/speakerEmbedding.d.ts +8 -0
  819. package/build/utils/speakerEmbedding.d.ts.map +1 -0
  820. package/build/utils/speakerEmbedding.js +63 -0
  821. package/build/utils/speakerEmbedding.js.map +1 -0
  822. package/build/utils/storage/index.d.ts +3 -0
  823. package/build/utils/storage/index.d.ts.map +1 -0
  824. package/build/utils/storage/index.js +3 -0
  825. package/build/utils/storage/index.js.map +1 -0
  826. package/build/utils/storage/storage.d.ts +24 -0
  827. package/build/utils/storage/storage.d.ts.map +1 -0
  828. package/build/utils/storage/storage.js +85 -0
  829. package/build/utils/storage/storage.js.map +1 -0
  830. package/build/utils/storage/zip.d.ts +3 -0
  831. package/build/utils/storage/zip.d.ts.map +1 -0
  832. package/build/utils/storage/zip.js +14 -0
  833. package/build/utils/storage/zip.js.map +1 -0
  834. package/build/utils/timers/index.d.ts +3 -0
  835. package/build/utils/timers/index.d.ts.map +1 -0
  836. package/build/utils/timers/index.js +3 -0
  837. package/build/utils/timers/index.js.map +1 -0
  838. package/build/utils/timers/timers.d.ts +9 -0
  839. package/build/utils/timers/timers.d.ts.map +1 -0
  840. package/build/utils/timers/timers.js +142 -0
  841. package/build/utils/timers/timers.js.map +1 -0
  842. package/package.json +122 -0
  843. package/src/assets/silence.wav +0 -0
  844. package/src/devtools.ts +13 -0
  845. package/src/engine.ts +198 -0
  846. package/src/facades/dev.ts +213 -0
  847. package/src/facades/display.ts +17 -0
  848. package/src/facades/displayMirror.ts +33 -0
  849. package/src/facades/gallery.ts +69 -0
  850. package/src/facades/glasses.ts +219 -0
  851. package/src/facades/glassesSettings.ts +57 -0
  852. package/src/facades/glassesWifi.ts +68 -0
  853. package/src/facades/maps.ts +13 -0
  854. package/src/facades/memory.ts +46 -0
  855. package/src/facades/miniapps.ts +62 -0
  856. package/src/facades/notifications.ts +14 -0
  857. package/src/facades/ota.ts +177 -0
  858. package/src/facades/pairing.ts +329 -0
  859. package/src/facades/permissions.ts +165 -0
  860. package/src/facades/phoneNotifications.ts +41 -0
  861. package/src/facades/providerKeys.ts +31 -0
  862. package/src/facades/reports.ts +180 -0
  863. package/src/facades/session.ts +54 -0
  864. package/src/facades/settings.ts +57 -0
  865. package/src/facades/speakers.ts +152 -0
  866. package/src/facades/speech.ts +49 -0
  867. package/src/index.ts +214 -0
  868. package/src/internal.ts +155 -0
  869. package/src/runtime/bootstrap.ts +159 -0
  870. package/src/runtime/config.ts +155 -0
  871. package/src/runtime/scanQrSeam.ts +20 -0
  872. package/src/runtime/streamConfig.ts +34 -0
  873. package/src/services/AiTierResolution.ts +106 -0
  874. package/src/services/AppRegistry.ts +1245 -0
  875. package/src/services/AudioCloudUplink.ts +56 -0
  876. package/src/services/AudioPlaybackService.ts +873 -0
  877. package/src/services/AutomaticReportResult.ts +50 -0
  878. package/src/services/BlobStore.ts +823 -0
  879. package/src/services/CaptionsTesterReportService.ts +107 -0
  880. package/src/services/CloudAudioSubscriptionSync.ts +53 -0
  881. package/src/services/CloudClientService.ts +1031 -0
  882. package/src/services/CloudTranscriptE2EMetrics.ts +27 -0
  883. package/src/services/ConnectionCoordinator.ts +65 -0
  884. package/src/services/DevServerBridge.ts +334 -0
  885. package/src/services/DeviceEventRouter.ts +266 -0
  886. package/src/services/DeviceStoreHydration.ts +130 -0
  887. package/src/services/DisplayProcessor.ts +775 -0
  888. package/src/services/ForegroundLocationPermission.ts +24 -0
  889. package/src/services/GlassesReadiness.ts +103 -0
  890. package/src/services/GlassesSettingsSync.ts +140 -0
  891. package/src/services/GlassesStatusProjection.ts +73 -0
  892. package/src/services/HeadingService.ts +122 -0
  893. package/src/services/HotspotOtaTransport.ts +184 -0
  894. package/src/services/HotspotShutdown.ts +41 -0
  895. package/src/services/JSCrashController.ts +189 -0
  896. package/src/services/JSCrashloopReportService.ts +66 -0
  897. package/src/services/JSLogPipeline.ts +166 -0
  898. package/src/services/JSRouter.ts +555 -0
  899. package/src/services/LocalDisplayManager.ts +815 -0
  900. package/src/services/LocalMiniappDataMigration.ts +102 -0
  901. package/src/services/LocalMiniappRuntime.ts +5747 -0
  902. package/src/services/LocalMiniappStorage.ts +82 -0
  903. package/src/services/LocalMiniappUserIdentity.ts +79 -0
  904. package/src/services/LocalSttFallbackCoordinator.ts +247 -0
  905. package/src/services/MapboxTokenService.ts +118 -0
  906. package/src/services/MemoryCapture.ts +61 -0
  907. package/src/services/MemoryStore.ts +633 -0
  908. package/src/services/MicStateCoordinator.ts +259 -0
  909. package/src/services/MiniappEngine.ts +143 -0
  910. package/src/services/MiniappLauncher.ts +365 -0
  911. package/src/services/MiniappLiveness.ts +24 -0
  912. package/src/services/MiniappRunningRegistry.ts +57 -0
  913. package/src/services/NativeHttpResponse.ts +6 -0
  914. package/src/services/NativeNavigationService.ts +176 -0
  915. package/src/services/NavigationHandlers.ts +527 -0
  916. package/src/services/NavigationService.ts +926 -0
  917. package/src/services/NotificationsEmitter.ts +49 -0
  918. package/src/services/OfflineSpeechModelService.ts +168 -0
  919. package/src/services/OtaArtifactDownloader.ts +312 -0
  920. package/src/services/OtaInstallCoordinator.ts +1707 -0
  921. package/src/services/OtaService.ts +89 -0
  922. package/src/services/OtaUpdateCheckService.ts +550 -0
  923. package/src/services/PairingIdentity.ts +154 -0
  924. package/src/services/PhoneCalendarHelpers.ts +212 -0
  925. package/src/services/PhoneCalendarService.ts +61 -0
  926. package/src/services/PhoneCameraFovCoordinator.ts +210 -0
  927. package/src/services/PhoneLocationService.ts +120 -0
  928. package/src/services/PhoneNotificationsSync.ts +46 -0
  929. package/src/services/PhonePhotoCoordinator.ts +435 -0
  930. package/src/services/PhoneStreamCoordinator.ts +874 -0
  931. package/src/services/PhoneVideoCoordinator.ts +203 -0
  932. package/src/services/ProviderKeyValidation.ts +57 -0
  933. package/src/services/STTModelManager.ts +434 -0
  934. package/src/services/SceneRenderer.ts +320 -0
  935. package/src/services/SentenceTtsPipeline.ts +106 -0
  936. package/src/services/SpeakerIdModelManager.ts +151 -0
  937. package/src/services/SpeakerIdSync.ts +79 -0
  938. package/src/services/SpeakerLabelResolver.ts +140 -0
  939. package/src/services/SpeakerProfileStore.ts +265 -0
  940. package/src/services/SpeakerSegmentResolver.ts +194 -0
  941. package/src/services/StreamLifecycleController.ts +203 -0
  942. package/src/services/SupportProfileSync.ts +218 -0
  943. package/src/services/TTSModelManager.ts +466 -0
  944. package/src/services/TranscriptionRouting.ts +47 -0
  945. package/src/services/TtsTextSanitizer.ts +66 -0
  946. package/src/services/UIRouter.ts +250 -0
  947. package/src/services/WebviewBridge.ts +50 -0
  948. package/src/services/WifiAdbRequest.ts +53 -0
  949. package/src/services/asg/GalleryMediaIntegrityReportService.ts +72 -0
  950. package/src/services/asg/asgCameraApi.ts +1786 -0
  951. package/src/services/asg/cameraRollExportCoordinator.ts +627 -0
  952. package/src/services/asg/cameraRollExportLedger.ts +122 -0
  953. package/src/services/asg/galleryMediaValidation.ts +185 -0
  954. package/src/services/asg/galleryNotices.ts +61 -0
  955. package/src/services/asg/galleryPathMigration.ts +22 -0
  956. package/src/services/asg/gallerySettingsService.ts +68 -0
  957. package/src/services/asg/gallerySyncNotifications.ts +235 -0
  958. package/src/services/asg/gallerySyncService.ts +2278 -0
  959. package/src/services/asg/galleryTransferLedger.ts +312 -0
  960. package/src/services/asg/localNetworkTransport.ts +169 -0
  961. package/src/services/asg/localStorageService.ts +652 -0
  962. package/src/services/asg/mediaProcessingQueue.ts +518 -0
  963. package/src/services/audioPlaybackAssets.ts +8 -0
  964. package/src/services/blobPaths.ts +86 -0
  965. package/src/services/cloudStreamApi.ts +142 -0
  966. package/src/services/effectiveCapabilities.ts +27 -0
  967. package/src/services/gallerySyncClock.ts +39 -0
  968. package/src/services/glassesClockSync.ts +115 -0
  969. package/src/services/manifestActions.ts +80 -0
  970. package/src/services/manifestVersionGate.ts +43 -0
  971. package/src/services/memoryStoreInstance.ts +70 -0
  972. package/src/services/navigation/roadNameResolver.ts +103 -0
  973. package/src/services/navigation/routesApiCodec.ts +69 -0
  974. package/src/services/otaDisplayState.ts +156 -0
  975. package/src/services/otaInstallPolicy.ts +132 -0
  976. package/src/services/otaLegacyMapping.ts +71 -0
  977. package/src/services/otaManifestUrl.ts +53 -0
  978. package/src/services/slimStreamStatus.ts +39 -0
  979. package/src/services/soniox/SonioxDirectService.ts +517 -0
  980. package/src/services/soniox/SonioxRealtimeClient.ts +349 -0
  981. package/src/services/soniox/SonioxTokenAssembler.ts +451 -0
  982. package/src/services/speakerProfileStoreInstance.ts +25 -0
  983. package/src/services/uiShim.ts +375 -0
  984. package/src/stores/apps.ts +626 -0
  985. package/src/stores/bluetoothSettingKeys.ts +35 -0
  986. package/src/stores/cloudClientStatus.ts +37 -0
  987. package/src/stores/connection.ts +69 -0
  988. package/src/stores/core.ts +32 -0
  989. package/src/stores/display.ts +121 -0
  990. package/src/stores/gallerySync.ts +384 -0
  991. package/src/stores/glasses.ts +314 -0
  992. package/src/stores/settings.ts +1299 -0
  993. package/src/stores/transcriptionDebug.ts +0 -0
  994. package/src/types/applet.ts +121 -0
  995. package/src/types/asg.ts +58 -0
  996. package/src/types/capabilities/even-realities-g1.ts +70 -0
  997. package/src/types/capabilities/even-realities-g2.ts +98 -0
  998. package/src/types/capabilities/mentra-display.ts +78 -0
  999. package/src/types/capabilities/mentra-live.ts +106 -0
  1000. package/src/types/capabilities/nimo.ts +84 -0
  1001. package/src/types/capabilities/none.ts +79 -0
  1002. package/src/types/capabilities/simulated-glasses.ts +95 -0
  1003. package/src/types/capabilities/vuzix-z100.ts +67 -0
  1004. package/src/types/enums.ts +27 -0
  1005. package/src/types/hardware.ts +220 -0
  1006. package/src/types/index.ts +38 -0
  1007. package/src/utils/GlobalEventEmitter.ts +15 -0
  1008. package/src/utils/cloudClient/RnUdpAdapter.ts +47 -0
  1009. package/src/utils/cloudClient/cloudSecureStore.ts +28 -0
  1010. package/src/utils/configuredDevHost.ts +17 -0
  1011. package/src/utils/debouncedPatch.ts +28 -0
  1012. package/src/utils/devLogging.ts +140 -0
  1013. package/src/utils/devMiniappLaunch.ts +196 -0
  1014. package/src/utils/diagnosticContext.ts +159 -0
  1015. package/src/utils/display/composer/ColumnComposer.ts +285 -0
  1016. package/src/utils/display/composer/index.ts +15 -0
  1017. package/src/utils/display/helpers/DisplayHelpers.ts +409 -0
  1018. package/src/utils/display/helpers/ScrollView.ts +326 -0
  1019. package/src/utils/display/helpers/index.ts +14 -0
  1020. package/src/utils/display/index.ts +273 -0
  1021. package/src/utils/display/measurer/TextMeasurer.ts +346 -0
  1022. package/src/utils/display/measurer/index.ts +21 -0
  1023. package/src/utils/display/measurer/script-detection.ts +218 -0
  1024. package/src/utils/display/profiles/g1.ts +236 -0
  1025. package/src/utils/display/profiles/g2.ts +39 -0
  1026. package/src/utils/display/profiles/index.ts +28 -0
  1027. package/src/utils/display/profiles/nex.ts +166 -0
  1028. package/src/utils/display/profiles/types.ts +132 -0
  1029. package/src/utils/display/profiles/z100.ts +198 -0
  1030. package/src/utils/display/scene/anchor.ts +39 -0
  1031. package/src/utils/display/scene/degrade.ts +80 -0
  1032. package/src/utils/display/scene/differ.ts +128 -0
  1033. package/src/utils/display/scene/index.ts +24 -0
  1034. package/src/utils/display/scene/process.ts +228 -0
  1035. package/src/utils/display/scene/store.ts +0 -0
  1036. package/src/utils/display/scene/types.ts +0 -0
  1037. package/src/utils/display/wrapper/TextWrapper.ts +546 -0
  1038. package/src/utils/display/wrapper/index.ts +15 -0
  1039. package/src/utils/display/wrapper/types.ts +102 -0
  1040. package/src/utils/e2eMetrics.ts +30 -0
  1041. package/src/utils/hardware/hardware.ts +171 -0
  1042. package/src/utils/hardware/index.ts +1 -0
  1043. package/src/utils/memory/search.ts +204 -0
  1044. package/src/utils/miniappDiagnostics.ts +226 -0
  1045. package/src/utils/miniappGlobals.ts +259 -0
  1046. package/src/utils/permissions/MediaLibraryPermissions.ts +161 -0
  1047. package/src/utils/permissions/galleryDisplayName.ts +28 -0
  1048. package/src/utils/speakerEmbedding.ts +58 -0
  1049. package/src/utils/storage/index.ts +2 -0
  1050. package/src/utils/storage/storage.ts +101 -0
  1051. package/src/utils/storage/zip.ts +13 -0
  1052. package/src/utils/timers/index.ts +2 -0
  1053. package/src/utils/timers/timers.ts +175 -0
@@ -0,0 +1,1707 @@
1
+ /**
2
+ * OtaInstallCoordinator — engine-owned OTA install state machine (WP 8B).
3
+ *
4
+ * Owns every timer/retry/sequencing rule that used to live in the host screen
5
+ * `mobile/src/app/ota/progress.tsx`: the global session timeout, serialized
6
+ * ota_start retries, the stuck-at-zero watchdog, the progress-stall watchdog keyed
7
+ * on a staleness signature, connect-edge arbitration (initial mount vs
8
+ * reconnect vs post-APK reboot), the ota_query_status reply fallback, the ping
9
+ * keepalive, the ota_start_ack / mtk_update_complete listeners, and terminal
10
+ * cleanup. The host progress screen is a pure renderer over
11
+ * `snapshot()`/`onSnapshot()` plus the `attach()/detach()/retry()/finish()`
12
+ * commands (exposed as `engine.ota.installSession`).
13
+ *
14
+ * The reaction pass mirrors the old component's effect ordering: a
15
+ * pass diffs the store-derived inputs (connected / otaStatus / otaProgress /
16
+ * displayState / stall signature) exactly like the old effect dep arrays, and
17
+ * state written mid-pass queues a follow-up pass — the same way a setState
18
+ * during React effects scheduled a re-render after the current effect batch.
19
+ */
20
+ import BluetoothSdk from "@glassly/bluetooth-sdk/internal"
21
+ import type {OtaProgress, OtaStatus} from "@glassly/bluetooth-sdk/internal"
22
+ import GlobalEventEmitter from "../utils/GlobalEventEmitter"
23
+ import {isGlassesConnected, useGlassesStore} from "../stores/glasses"
24
+ import {resolveOtaManifestUrl} from "./otaManifestUrl"
25
+ import {hotspotOtaTransport, type HotspotOtaPhase} from "./HotspotOtaTransport"
26
+ import type {OtaCheckCurrentGlassesResult} from "./OtaUpdateCheckService"
27
+ import {deriveDisplayState, type DisplayState} from "./otaDisplayState"
28
+ import {
29
+ BES_CONTINUE_LOCKOUT_MS,
30
+ DOWNLOAD_STUCK_TIMEOUT_MS,
31
+ GLOBAL_OTA_TIMEOUT_MS,
32
+ LEGACY_APK_COMPLETION_SETTLE_MS,
33
+ LEGACY_BES_CONTINUE_LOCKOUT_MS,
34
+ LEGACY_DOWNLOAD_STUCK_TIMEOUT_MS,
35
+ LEGACY_GLOBAL_OTA_TIMEOUT_MS,
36
+ LEGACY_MTK_INSTALL_TIMEOUT_MS,
37
+ LEGACY_MTK_SIM_CAP_PERCENT,
38
+ LEGACY_MTK_SIM_FLOOR_PERCENT,
39
+ LEGACY_MTK_SIM_TICK_MS,
40
+ LEGACY_MTK_STALL_DETECT_MS,
41
+ LEGACY_MTK_STALL_ZONE_MAX_PERCENT,
42
+ LEGACY_MTK_STALL_ZONE_MIN_PERCENT,
43
+ LEGACY_PING_INTERVAL_MS,
44
+ LEGACY_PROGRESS_TIMEOUT_MS,
45
+ LEGACY_RETRY_INTERVAL_MS,
46
+ MAX_RETRIES,
47
+ MTK_INSTALL_TIMEOUT_MS,
48
+ OtaProgressMessages,
49
+ PING_INTERVAL_MS,
50
+ PROGRESS_TIMEOUT_MS,
51
+ QUERY_REPLY_TIMEOUT_MS,
52
+ RETRY_INTERVAL_MS,
53
+ isLegacyShapedOtaSession,
54
+ isLegacyShapedOtaStatus,
55
+ selectOtaProtocolProfile,
56
+ type OtaProtocolProfile,
57
+ } from "./otaInstallPolicy"
58
+
59
+ function isTerminalForWatchdog(d: DisplayState): boolean {
60
+ return d === "complete" || d === "failed" || d === "restarting"
61
+ }
62
+
63
+ function hotspotPreflightErrorMessage(error: unknown): string {
64
+ const code =
65
+ typeof error === "object" && error !== null && "code" in error ? String((error as {code: unknown}).code) : ""
66
+ switch (code) {
67
+ case "artifact_download_failed":
68
+ return OtaProgressMessages.hotspotArtifactDownloadFailed
69
+ case "artifact_verify_failed":
70
+ case "manifest_invalid":
71
+ return OtaProgressMessages.hotspotArtifactVerifyFailed
72
+ case "hotspot_wifi_permission_denied":
73
+ return OtaProgressMessages.hotspotWifiPermissionDenied
74
+ case "hotspot_start_failed":
75
+ return OtaProgressMessages.hotspotStartFailed
76
+ case "hotspot_join_failed":
77
+ return OtaProgressMessages.hotspotJoinFailed
78
+ case "hotspot_server_failed":
79
+ return OtaProgressMessages.hotspotServerFailed
80
+ default:
81
+ return OtaProgressMessages.hotspotServerFailed
82
+ }
83
+ }
84
+
85
+ /** Non-empty when glasses are actively reporting work — drives stall timeout reset. */
86
+ function buildProgressStalenessSignature(
87
+ otaStatus: OtaStatus | null,
88
+ otaProgress: OtaProgress | null,
89
+ displayState: DisplayState,
90
+ ): string {
91
+ if (displayState !== "starting" && displayState !== "updating") return ""
92
+ if (otaStatus && (otaStatus.status === "in_progress" || otaStatus.status === "step_complete")) {
93
+ return `s:${otaStatus.sessionId}|${otaStatus.status}|${otaStatus.phase}|${otaStatus.stepType}|${otaStatus.stepPercent}|${otaStatus.overallPercent}`
94
+ }
95
+ // Legacy-shaped BES download FINISHED (WP 8C): the legacy screen kept a progress
96
+ // watchdog armed while waiting for the BES install events to start; keep the stall
97
+ // watchdog armed here too so a wedged flash still fails instead of hanging.
98
+ if (
99
+ isLegacyShapedOtaStatus(otaStatus) &&
100
+ otaStatus?.stepType === "bes" &&
101
+ otaStatus.phase === "download" &&
102
+ otaStatus.status === "complete"
103
+ ) {
104
+ return "s:legacy-bes-download-complete-await-install"
105
+ }
106
+ if (otaProgress && (otaProgress.status === "PROGRESS" || otaProgress.status === "STARTED")) {
107
+ return `p:${otaProgress.currentUpdate}|${otaProgress.stage}|${otaProgress.status}|${otaProgress.progress}`
108
+ }
109
+ return ""
110
+ }
111
+
112
+ function progressTimeoutDurationMs(
113
+ otaStatus: OtaStatus | null,
114
+ otaProgress: OtaProgress | null,
115
+ legacySession: boolean,
116
+ ): number {
117
+ if (otaStatus?.stepType === "mtk" && otaStatus.phase === "install") {
118
+ return legacySession ? LEGACY_MTK_INSTALL_TIMEOUT_MS : MTK_INSTALL_TIMEOUT_MS
119
+ }
120
+ if (otaProgress?.currentUpdate === "mtk" && otaProgress.stage === "install") {
121
+ return legacySession ? LEGACY_MTK_INSTALL_TIMEOUT_MS : MTK_INSTALL_TIMEOUT_MS
122
+ }
123
+ return legacySession ? LEGACY_PROGRESS_TIMEOUT_MS : PROGRESS_TIMEOUT_MS
124
+ }
125
+
126
+ function latestPercentForStuck(otaStatus: OtaStatus | null, otaProgress: OtaProgress | null): number {
127
+ if (otaProgress?.progress != null) return otaProgress.progress
128
+ if (otaStatus?.status === "in_progress" || otaStatus?.status === "step_complete") {
129
+ return Math.max(otaStatus.overallPercent ?? 0, otaStatus.stepPercent ?? 0)
130
+ }
131
+ return 0
132
+ }
133
+
134
+ /**
135
+ * "idle" ota_status means the glasses have no active session — that is NOT a
136
+ * recovery signal, so the query-reply fallback must keep firing and retry
137
+ * ota_start. Only treat real progress or a non-idle ota_status as a useful
138
+ * reply that cancels the fallback.
139
+ */
140
+ function hasRecoveringOtaReply(otaStatus: OtaStatus | null, otaProgress: OtaProgress | null): boolean {
141
+ if (otaProgress) return true
142
+ return !!otaStatus && otaStatus.status !== "idle"
143
+ }
144
+
145
+ /** Read model the host progress screen renders from. */
146
+ export interface OtaInstallSnapshot {
147
+ displayState: DisplayState
148
+ errorMsg: string
149
+ continueButtonDisabled: boolean
150
+ connected: boolean
151
+ otaStatus: OtaStatus | null
152
+ otaProgress: OtaProgress | null
153
+ /**
154
+ * Display-only simulated percent for legacy (< 37) MTK install stalls (WP 8C-e), or
155
+ * null. The MTK system install goes quiet around 49-50%; the legacy screen simulated
156
+ * +1% ticks up to 60% purely to reassure the user. Render max(real, simulated).
157
+ */
158
+ mtkInstallStallSimulatedPercent: number | null
159
+ /** True for a downgrade (version-change) session — the UI narrates it differently. */
160
+ isVersionChange: boolean
161
+ /** True once the glasses reported the exact pinned target (the downgrade truly completed). */
162
+ versionChangeConverged: boolean
163
+ /**
164
+ * Sub-phase of the downgrade detour for the progress narrative, or null when not
165
+ * in one: "installing" before the handoff, "restarting" while the glasses are dark
166
+ * (uninstall / factory flicker / target install), "verifying" once reconnected and
167
+ * checking the version. Completion is the "complete" displayState.
168
+ */
169
+ versionChangePhase: "installing" | "restarting" | "verifying" | null
170
+ /** Pre-ota_start phone staging/join state for a hotspot attempt. */
171
+ hotspotPhase: HotspotOtaPhase
172
+ hotspotArtifactPercent: number | null
173
+ }
174
+
175
+ type OtaStartOutcome = "pending" | "acknowledged" | "rejected"
176
+
177
+ interface OtaStartOwnership {
178
+ outcome: OtaStartOutcome
179
+ promise: Promise<void>
180
+ }
181
+
182
+ class OtaInstallCoordinator {
183
+ private attached = false
184
+ private preparedCheckResult: OtaCheckCurrentGlassesResult | null = null
185
+ private selectedTransport: "wifi" | "hotspot" = "wifi"
186
+ private hotspotManifestUrl: string | null = null
187
+ private hotspotPhase: HotspotOtaPhase = "idle"
188
+ private hotspotArtifactPercent: number | null = null
189
+
190
+ // Genuinely session-local state (was component state/refs).
191
+ private errorMsg = ""
192
+ private sawReconnectEdge = false
193
+ private continueButtonDisabled = false
194
+ // True once this session reported an APK step. After an APK install the ASG
195
+ // process restarts with a new build number, so finish() must clear the stale
196
+ // one before check-for-updates re-checks (mirrors the legacy screen).
197
+ private apkStepSeen = false
198
+ private prevConnected = false
199
+
200
+ // Legacy (< 37) compatibility state, ported from progress-legacy.tsx (WP 8C).
201
+ // Build number at session start — legacy APK completion is detected by the build
202
+ // number increasing past it when the explicit reconnect status never arrives.
203
+ private initialBuildNumber: string | null = null
204
+ // Captured with initialBuildNumber: whether the selected update includes an APK step.
205
+ private apkExpectedInSession = false
206
+ // Latched when the legacy build-number completion fires; projected as "complete".
207
+ private apkCompletedViaBuildIncrease = false
208
+ // The split routes selected legacy/unified behavior before navigation. Keep
209
+ // that choice sticky while an APK step upgrades ASG mid-flow.
210
+ private protocolProfile: OtaProtocolProfile | null = null
211
+
212
+ // BES reboot recovery. Native bridges normalize explicit BES success to a
213
+ // terminal status; pre-37 flows additionally retain the legacy screen's
214
+ // lost-FINISHED fallback. Completion still requires a BES-specific observed
215
+ // disconnect -> reconnect lifecycle, never the generic session reconnect flag.
216
+ private besRestartExpected = false
217
+ private besRestartDisconnectObserved = false
218
+ private besRestartRecovery: "awaiting" | "complete" | null = null
219
+
220
+ // Downgrade detour (version-change mode). Captured at attach from the selected
221
+ // update: the pinned target is a LOWER build, executed by the recovery worker via
222
+ // uninstall->reinstall, so the phone must NOT drive ota_start on the intermediate
223
+ // reconnect (the passive factory build) and completion is exact-version
224
+ // convergence, not a build-number increase.
225
+ private versionChangeSession = false
226
+ private versionChangeTarget: number | null = null
227
+ // Set once ASG reports the apk install has started (the handoff point after which
228
+ // ASG dies and the recovery worker owns the transaction).
229
+ private versionChangeInstallStarted = false
230
+ // Latched when a reconnect reports buildNumber === versionChangeTarget.
231
+ private versionChangeConverged = false
232
+ // Holds a legacy apk install FINISHED out of "complete" for the settle window.
233
+ private legacyApkSettleHold = false
234
+ // Display-only legacy MTK install stall simulation.
235
+ private mtkSimulatedPercent: number | null = null
236
+ private lastRealMtkProgress = 0
237
+
238
+ // Timer handles
239
+ private globalTimeout: ReturnType<typeof setTimeout> | null = null
240
+ private globalTimeoutStarted = false
241
+ private retryTimeout: ReturnType<typeof setTimeout> | null = null
242
+ private stuckTimeout: ReturnType<typeof setTimeout> | null = null
243
+ private progressTimeout: ReturnType<typeof setTimeout> | null = null
244
+ private pingInterval: ReturnType<typeof setInterval> | null = null
245
+ private queryReplyTimeout: ReturnType<typeof setTimeout> | null = null
246
+ private continueLockoutTimer: ReturnType<typeof setTimeout> | null = null
247
+ private legacyApkSettleTimer: ReturnType<typeof setTimeout> | null = null
248
+ private mtkStallDetectTimer: ReturnType<typeof setTimeout> | null = null
249
+ private mtkSimTickTimer: ReturnType<typeof setInterval> | null = null
250
+
251
+ // Request / retry bookkeeping (no glasses source)
252
+ // At most one apk-install status poll in flight (native rejects concurrent queries).
253
+ private apkInstallPollInFlight = false
254
+ // The native SDK owns one ota_start request until its application-level ack or
255
+ // timeout. Keep pending ownership across attach/detach, and retain the settled
256
+ // outcome while attached so Retry/query fallback cannot reinterpret an accepted
257
+ // command as permission to start a second OTA transaction.
258
+ private otaStartOwnership: OtaStartOwnership | null = null
259
+ private hasFirstActivity = false
260
+ // Stuck-at-zero watchdog clears only on first NON-ZERO progress; "first activity"
261
+ // alone (e.g. ota_status with stepPercent=0) used to clear it too eagerly.
262
+ private hasFirstNonZeroProgress = false
263
+ private retryCount = 0
264
+
265
+ // Log displayState transitions only (not every pass).
266
+ private prevDisplayState: DisplayState | null = null
267
+
268
+ // Last-handled reaction inputs — the equivalents of the old effect dep arrays.
269
+ private lastConnected = false
270
+ private lastOtaStatus: OtaStatus | null = null
271
+ private lastOtaProgress: OtaProgress | null = null
272
+ private lastBuildNumber = ""
273
+ private lastDisplayState: DisplayState | null = null
274
+ private lastStallSig = ""
275
+
276
+ // Reaction pass re-entrancy guard (mirrors React deferring a setState-during-
277
+ // effects re-render until the current effect batch finished).
278
+ private reacting = false
279
+ private reactQueued = false
280
+
281
+ private storeUnsubscribe: (() => void) | null = null
282
+ private snapshotCheckers = new Set<() => void>()
283
+
284
+ // --- public surface (engine.ota.installSession) ---
285
+
286
+ /** Select the transport at the existing install entry point before the progress route attaches. */
287
+ prepare(checkResult: OtaCheckCurrentGlassesResult): "wifi" | "hotspot" {
288
+ const state = useGlassesStore.getState()
289
+ if (!state.wifiStatusKnown) {
290
+ throw new Error("Glasses Wi-Fi status is not available")
291
+ }
292
+ const hotspotSupported = state.hotspotOtaVersion === 1
293
+ const wifiConnected = state.wifi.state === "connected"
294
+ if (!wifiConnected && !hotspotSupported) {
295
+ throw new Error("Connected glasses require Wi-Fi for OTA")
296
+ }
297
+ this.preparedCheckResult = checkResult
298
+ this.selectedTransport = wifiConnected ? "wifi" : "hotspot"
299
+ this.hotspotManifestUrl = null
300
+ this.hotspotPhase = "idle"
301
+ this.hotspotArtifactPercent = null
302
+ return this.selectedTransport
303
+ }
304
+
305
+ /**
306
+ * Bind the state machine to a mounted progress screen. Idempotent per
307
+ * attach/detach cycle. Runs the initial-mount arbitration (send ota_start
308
+ * when connected with no session; otherwise ota_query_status + reply
309
+ * fallback) and starts reacting to store changes + BLE OTA events.
310
+ */
311
+ attach(): void {
312
+ if (this.attached) return
313
+ this.attached = true
314
+ this.resetSessionState()
315
+ const initialState = useGlassesStore.getState()
316
+ this.protocolProfile = selectOtaProtocolProfile(
317
+ initialState.otaStatus,
318
+ initialState.otaProgress,
319
+ initialState.buildNumber,
320
+ )
321
+ const selected = initialState.otaUpdateAvailable
322
+ if (selected?.isDowngrade && typeof selected.versionCode === "number" && selected.versionCode > 0) {
323
+ this.versionChangeSession = true
324
+ this.versionChangeTarget = selected.versionCode
325
+ console.log(`[OTA_PROGRESS] version-change (downgrade) session, target=${selected.versionCode}`)
326
+ }
327
+ this.prevConnected = isGlassesConnected(useGlassesStore.getState().connection)
328
+ this.storeUnsubscribe = useGlassesStore.subscribe(() => this.react())
329
+ // Mount pass: every reaction block runs once (like all effects on mount),
330
+ // then any passes queued by state written during the blocks.
331
+ this.reacting = true
332
+ try {
333
+ this.runPass(true)
334
+ while (this.reactQueued) {
335
+ this.reactQueued = false
336
+ this.runPass(false)
337
+ }
338
+ } finally {
339
+ this.reacting = false
340
+ }
341
+ }
342
+
343
+ /**
344
+ * Unbind on screen unmount: clears ALL timers/subscriptions/listeners and
345
+ * resets the session-local state (the old component state died with the
346
+ * component) so a re-attach is a fresh session.
347
+ */
348
+ detach(): void {
349
+ if (!this.attached) return
350
+ this.attached = false
351
+ if (this.storeUnsubscribe) {
352
+ this.storeUnsubscribe()
353
+ this.storeUnsubscribe = null
354
+ }
355
+ GlobalEventEmitter.off("ota_start_ack", this.handleAck)
356
+ GlobalEventEmitter.off("mtk_update_complete", this.handleMtkComplete)
357
+ GlobalEventEmitter.off("glasses_session_changed", this.handleGlassesSessionChanged)
358
+ this.clearAllOtaTimers()
359
+ this.clearContinueLockoutTimer()
360
+ // Only native command ownership survives a remount. Once the command has
361
+ // settled, the glasses store/durable ota_status is the session authority.
362
+ if (this.otaStartOwnership?.outcome !== "pending") {
363
+ this.otaStartOwnership = null
364
+ }
365
+ this.resetSessionState()
366
+ }
367
+
368
+ /** Retry after a failure: clear state and re-send ota_start (if connected). */
369
+ retry(): void {
370
+ // While the recovery worker owns the detour (apk install latched, not yet
371
+ // converged), the phone must not drive: an ota_start now would push the
372
+ // factory/surviving build into a parallel download-install, and a second
373
+ // handoff would begin()/REPLACE the live transaction under the running
374
+ // worker. Retry just re-enters the reconcile wait — convergence or the
375
+ // session timeout decides the real outcome.
376
+ if (this.isInVersionChangeDetour()) {
377
+ console.log("[OTA_PROGRESS] retry during version-change detour — re-entering wait (no ota_start)")
378
+ this.setErrorMsg("")
379
+ this.emitInternalChange()
380
+ return
381
+ }
382
+ const store = useGlassesStore.getState()
383
+ const otaStatus = store.otaStatus
384
+ const otaProgress = store.otaProgress
385
+ const authoritativeFailure = otaStatus?.status === "failed" || otaProgress?.status === "FAILED"
386
+ const hasUnfinishedSession =
387
+ (!!otaStatus &&
388
+ otaStatus.status !== "idle" &&
389
+ otaStatus.status !== "failed" &&
390
+ otaStatus.status !== "complete") ||
391
+ (!!otaProgress && otaProgress.status !== "FAILED" && otaProgress.status !== "FINISHED")
392
+ const shouldReconcile =
393
+ this.otaStartOwnership?.outcome === "pending" ||
394
+ (!authoritativeFailure &&
395
+ (this.otaStartOwnership?.outcome === "acknowledged" || this.hasFirstActivity || hasUnfinishedSession))
396
+
397
+ if (shouldReconcile) {
398
+ console.log("[OTA_PROGRESS] retry pressed for an existing OTA session — reconciling without ota_start")
399
+ this.clearPerStepTimers()
400
+ this.clearLegacyApkSettleTimer()
401
+ this.clearMtkSimulationTimers()
402
+ this.clearContinueLockoutTimer()
403
+ this.setErrorMsg("")
404
+ this.maybeStartGlobalTimeout()
405
+ this.maybeArmStuckWatchdog()
406
+ if (isGlassesConnected(store.connection)) {
407
+ if (this.otaStartOwnership?.outcome === "pending") {
408
+ void this.sendOtaStartWithWatchdogs()
409
+ } else {
410
+ void BluetoothSdk.sendOtaQueryStatus().catch(() => {})
411
+ this.armQueryReplyFallback("retry")
412
+ }
413
+ }
414
+ return
415
+ }
416
+
417
+ console.log("[OTA_PROGRESS] retry pressed after a rejected or terminal attempt — starting a fresh ota_start")
418
+ // Batch like the old event handler: React ran the whole handler (including
419
+ // the ota_start send) before re-rendering + re-running effects.
420
+ const wasReacting = this.reacting
421
+ this.reacting = true
422
+ try {
423
+ this.clearPerStepTimers()
424
+ this.clearLegacyApkSettleTimer()
425
+ this.clearMtkSimulationTimers()
426
+ this.clearContinueLockoutTimer()
427
+ this.retryCount = 0
428
+ if (!this.prepareFreshOtaStartAttempt()) return
429
+ this.resetBesRestartAttempt()
430
+ this.setSawReconnectEdge(false)
431
+ this.setErrorMsg("")
432
+ this.setContinueButtonDisabled(false)
433
+ this.setLegacyApkSettleHold(false)
434
+ this.setMtkSimulatedPercent(null)
435
+ this.lastRealMtkProgress = 0
436
+ store.setOtaProgress(null)
437
+ store.setOtaStatus(null)
438
+ if (isGlassesConnected(useGlassesStore.getState().connection)) {
439
+ void this.sendOtaStartWithWatchdogs()
440
+ }
441
+ } finally {
442
+ this.reacting = wasReacting
443
+ }
444
+ if (!wasReacting) this.react()
445
+ }
446
+
447
+ /**
448
+ * Terminal-state cleanup half of the old Continue/Done handlers (navigation
449
+ * stays in the screen): clear the selected update, and after an APK step
450
+ * clear the stale build number so the next check re-reads version_info.
451
+ */
452
+ finish(): void {
453
+ if (this.otaStartOwnership?.outcome !== "pending") {
454
+ this.otaStartOwnership = null
455
+ }
456
+ useGlassesStore.getState().setOtaUpdateAvailable(null)
457
+ if (this.apkStepSeen) {
458
+ BluetoothSdk.updateGlasses({buildNumber: ""})
459
+ useGlassesStore.getState().setGlassesInfo({buildNumber: ""})
460
+ }
461
+ void this.teardownHotspotTransport()
462
+ }
463
+
464
+ /**
465
+ * Abandon an interrupted session (the super-mode skip): terminal cleanup
466
+ * plus dropping the session's status/progress read-state, so
467
+ * deriveDisplayState doesn't resurrect the stale install when the host
468
+ * navigates away and back through /ota routes.
469
+ */
470
+ discard(): void {
471
+ this.finish()
472
+ const store = useGlassesStore.getState()
473
+ store.setOtaStatus(null)
474
+ store.setOtaProgress(null)
475
+ }
476
+
477
+ snapshot(): OtaInstallSnapshot {
478
+ const state = useGlassesStore.getState()
479
+ const connected = isGlassesConnected(state.connection)
480
+ const otaStatus = state.otaStatus
481
+ const otaProgress = state.otaProgress
482
+ return {
483
+ displayState: deriveDisplayState({
484
+ otaStatus,
485
+ otaProgress,
486
+ connected,
487
+ errorMsg: this.errorMsg,
488
+ sawReconnectEdge: this.sawReconnectEdge,
489
+ legacyApkSettleHold: this.legacyApkSettleHold,
490
+ apkCompletedViaBuildIncrease: this.apkCompletedViaBuildIncrease,
491
+ besRestartRecovery: this.besRestartRecovery,
492
+ versionChangeConverged: this.versionChangeConverged,
493
+ versionChangeSession: this.versionChangeSession,
494
+ }),
495
+ errorMsg: this.errorMsg,
496
+ // Keep Continue disabled while the expected BES reboot is still in flight.
497
+ continueButtonDisabled: this.continueButtonDisabled || this.besRestartRecovery === "awaiting",
498
+ connected,
499
+ // Copies: the snapshot must not hand callers mutable references into the store.
500
+ otaStatus: otaStatus ? {...otaStatus} : null,
501
+ otaProgress: otaProgress ? {...otaProgress} : null,
502
+ mtkInstallStallSimulatedPercent: this.mtkSimulatedPercent,
503
+ isVersionChange: this.versionChangeSession,
504
+ versionChangeConverged: this.versionChangeConverged,
505
+ versionChangePhase: this.deriveVersionChangePhase(connected),
506
+ hotspotPhase: this.hotspotPhase,
507
+ hotspotArtifactPercent: this.hotspotArtifactPercent,
508
+ }
509
+ }
510
+
511
+ /**
512
+ * True while the recovery worker owns the downgrade transaction (after the apk install
513
+ * started, before convergence). During this window ASG is being uninstalled/reinstalled and
514
+ * cannot answer the phone, so the coordinator must not ping, poll ota_query_status, or arm the
515
+ * progress-stall watchdog — doing so drives the passive factory build and fails the session
516
+ * before the target reconnects. Completion is exact-version convergence, detected separately.
517
+ */
518
+ private isInVersionChangeDetour(): boolean {
519
+ return this.versionChangeSession && this.versionChangeInstallStarted && !this.versionChangeConverged
520
+ }
521
+
522
+ /** Downgrade-detour sub-phase for the progress narrative (see OtaInstallSnapshot). */
523
+ private deriveVersionChangePhase(connected: boolean): "installing" | "restarting" | "verifying" | null {
524
+ if (!this.versionChangeSession || this.versionChangeConverged) return null
525
+ if (!this.versionChangeInstallStarted) return "installing"
526
+ return connected ? "verifying" : "restarting"
527
+ }
528
+
529
+ /**
530
+ * Subscribe to install snapshot changes, deduped on the projected JSON.
531
+ * Fires on glasses-store changes AND coordinator-internal state changes
532
+ * (errorMsg / reconnect edge / continue lockout). Returns an unsubscribe.
533
+ */
534
+ onSnapshot(cb: (snapshot: OtaInstallSnapshot) => void): () => void {
535
+ let last = JSON.stringify(this.snapshot())
536
+ const check = () => {
537
+ const snap = this.snapshot()
538
+ const key = JSON.stringify(snap)
539
+ if (key === last) return
540
+ last = key
541
+ cb(snap)
542
+ }
543
+ this.snapshotCheckers.add(check)
544
+ const unsubscribeStore = useGlassesStore.subscribe(check)
545
+ return () => {
546
+ this.snapshotCheckers.delete(check)
547
+ unsubscribeStore()
548
+ }
549
+ }
550
+
551
+ // --- internal state plumbing ---
552
+
553
+ private resetSessionState(): void {
554
+ this.errorMsg = ""
555
+ this.sawReconnectEdge = false
556
+ this.continueButtonDisabled = false
557
+ this.apkStepSeen = false
558
+ this.prevConnected = false
559
+ this.initialBuildNumber = null
560
+ this.apkExpectedInSession = false
561
+ this.apkCompletedViaBuildIncrease = false
562
+ this.protocolProfile = null
563
+ this.resetBesRestartAttempt()
564
+ this.versionChangeSession = false
565
+ this.versionChangeTarget = null
566
+ this.versionChangeInstallStarted = false
567
+ this.versionChangeConverged = false
568
+ this.legacyApkSettleHold = false
569
+ this.mtkSimulatedPercent = null
570
+ this.lastRealMtkProgress = 0
571
+ this.apkInstallPollInFlight = false
572
+ this.hasFirstActivity = false
573
+ this.hasFirstNonZeroProgress = false
574
+ this.retryCount = 0
575
+ this.globalTimeoutStarted = false
576
+ this.prevDisplayState = null
577
+ this.lastConnected = false
578
+ this.lastOtaStatus = null
579
+ this.lastOtaProgress = null
580
+ this.lastBuildNumber = ""
581
+ this.lastDisplayState = null
582
+ this.lastStallSig = ""
583
+ this.reactQueued = false
584
+ }
585
+
586
+ /** Begin a genuinely new command attempt only after native ownership has settled. */
587
+ private prepareFreshOtaStartAttempt(): boolean {
588
+ if (this.otaStartOwnership?.outcome === "pending") {
589
+ console.error("[OTA_PROGRESS] refused fresh ota_start while native still owns the previous request")
590
+ return false
591
+ }
592
+ this.otaStartOwnership = null
593
+ this.hasFirstActivity = false
594
+ this.hasFirstNonZeroProgress = false
595
+ this.setApkCompletedViaBuildIncrease(false)
596
+ return true
597
+ }
598
+
599
+ /** Reset only the reboot lifecycle that belongs to one install attempt. */
600
+ private resetBesRestartAttempt(): void {
601
+ this.besRestartExpected = false
602
+ this.besRestartDisconnectObserved = false
603
+ this.besRestartRecovery = null
604
+ }
605
+
606
+ /** Notify snapshot subscribers of a coordinator-internal state change. */
607
+ private emitInternalChange(): void {
608
+ for (const check of Array.from(this.snapshotCheckers)) check()
609
+ }
610
+
611
+ // Setters mirror the old setState calls: bail on identical values (React's
612
+ // setState bailout), notify snapshot subscribers, and queue a reaction pass.
613
+ private setErrorMsg(next: string): void {
614
+ if (this.errorMsg === next) return
615
+ this.errorMsg = next
616
+ this.emitInternalChange()
617
+ this.react()
618
+ }
619
+
620
+ private setSawReconnectEdge(next: boolean): void {
621
+ if (this.sawReconnectEdge === next) return
622
+ this.sawReconnectEdge = next
623
+ this.emitInternalChange()
624
+ this.react()
625
+ }
626
+
627
+ private setContinueButtonDisabled(next: boolean): void {
628
+ if (this.continueButtonDisabled === next) return
629
+ this.continueButtonDisabled = next
630
+ this.emitInternalChange()
631
+ this.react()
632
+ }
633
+
634
+ private setApkCompletedViaBuildIncrease(next: boolean): void {
635
+ if (this.apkCompletedViaBuildIncrease === next) return
636
+ this.apkCompletedViaBuildIncrease = next
637
+ this.emitInternalChange()
638
+ this.react()
639
+ }
640
+
641
+ private setLegacyApkSettleHold(next: boolean): void {
642
+ if (this.legacyApkSettleHold === next) return
643
+ this.legacyApkSettleHold = next
644
+ this.emitInternalChange()
645
+ this.react()
646
+ }
647
+
648
+ /** Display-only (WP 8C-e): notifies snapshot subscribers but never queues a reaction pass. */
649
+ private setMtkSimulatedPercent(next: number | null): void {
650
+ if (this.mtkSimulatedPercent === next) return
651
+ this.mtkSimulatedPercent = next
652
+ this.emitInternalChange()
653
+ }
654
+
655
+ /** Legacy/unified policy is selected once, matching the old sticky route choice. */
656
+ private isLegacySessionShapeNow(): boolean {
657
+ if (this.protocolProfile) return this.protocolProfile === "legacy"
658
+ const state = useGlassesStore.getState()
659
+ const selected = selectOtaProtocolProfile(
660
+ state.otaStatus,
661
+ state.otaProgress,
662
+ this.initialBuildNumber || state.buildNumber,
663
+ )
664
+ if (selected) {
665
+ this.protocolProfile = selected
666
+ console.log(`[OTA_PROGRESS] protocol profile selected: ${selected}`)
667
+ return selected === "legacy"
668
+ }
669
+ return isLegacyShapedOtaSession(state.otaStatus, state.otaProgress, state.buildNumber)
670
+ }
671
+
672
+ private react(): void {
673
+ if (!this.attached) return
674
+ if (this.reacting) {
675
+ this.reactQueued = true
676
+ return
677
+ }
678
+ this.reacting = true
679
+ try {
680
+ do {
681
+ this.reactQueued = false
682
+ this.runPass(false)
683
+ } while (this.reactQueued)
684
+ } finally {
685
+ this.reacting = false
686
+ }
687
+ }
688
+
689
+ /**
690
+ * One reaction pass = one render + its changed-dep effects of the old
691
+ * component, in the old declaration order.
692
+ */
693
+ private runPass(isMount: boolean): void {
694
+ const state = useGlassesStore.getState()
695
+ const connected = isGlassesConnected(state.connection)
696
+ const otaStatus = state.otaStatus
697
+ const otaProgress = state.otaProgress
698
+ const buildNumber = state.buildNumber
699
+
700
+ const connectedChanged = isMount || connected !== this.lastConnected
701
+ const otaStatusChanged = isMount || otaStatus !== this.lastOtaStatus
702
+ const otaProgressChanged = isMount || otaProgress !== this.lastOtaProgress
703
+ const buildNumberChanged = isMount || buildNumber !== this.lastBuildNumber
704
+
705
+ // Session-start build capture (legacy build-number APK completion, WP 8C-c) —
706
+ // mirrors the legacy screen capturing initialBuildNumber on its first truthy value,
707
+ // marked for APK only when the selected update sequence includes an apk step.
708
+ if (!this.initialBuildNumber && buildNumber) {
709
+ this.initialBuildNumber = buildNumber
710
+ this.apkExpectedInSession = !!state.otaUpdateAvailable?.updates?.includes("apk")
711
+ }
712
+
713
+ // Legacy pre-derivation compat (WP 8C): must run BEFORE deriveDisplayState so a
714
+ // legacy apk install "complete" is held by the settle window in the same pass it
715
+ // lands (never leaking a premature terminal pass to the watchdog blocks below).
716
+ if (otaStatusChanged) {
717
+ this.handleLegacyInstallCompleteEdge(otaStatus)
718
+ }
719
+
720
+ const legacySession = this.isLegacySessionShapeNow()
721
+
722
+ if (this.isBesRestartExpected(otaStatus, otaProgress, legacySession)) {
723
+ this.besRestartExpected = true
724
+ }
725
+
726
+ // Port the legacy route's actual reboot invariant before deriving display
727
+ // state, so reconnecting with stale BES in_progress@100 never renders updating.
728
+ if (connectedChanged && !connected && this.besRestartExpected && this.besRestartRecovery === null) {
729
+ this.besRestartDisconnectObserved = true
730
+ this.besRestartRecovery = "awaiting"
731
+ console.log("[OTA_PROGRESS] BES restart disconnect observed — awaiting reconnect verification")
732
+ }
733
+ if (this.besRestartRecovery === "awaiting" && connected) this.completeBesRestartRecovery()
734
+
735
+ // Derived UI state — the glasses data IS the source of truth.
736
+ const displayState = deriveDisplayState({
737
+ otaStatus,
738
+ otaProgress,
739
+ connected,
740
+ errorMsg: this.errorMsg,
741
+ sawReconnectEdge: this.sawReconnectEdge,
742
+ legacyApkSettleHold: this.legacyApkSettleHold,
743
+ apkCompletedViaBuildIncrease: this.apkCompletedViaBuildIncrease,
744
+ besRestartRecovery: this.besRestartRecovery,
745
+ versionChangeConverged: this.versionChangeConverged,
746
+ versionChangeSession: this.versionChangeSession,
747
+ })
748
+ const stallSig = buildProgressStalenessSignature(otaStatus, otaProgress, displayState)
749
+ const stallDuration = progressTimeoutDurationMs(otaStatus, otaProgress, legacySession)
750
+
751
+ const displayStateChanged = isMount || displayState !== this.lastDisplayState
752
+ const stallSigChanged = isMount || stallSig !== this.lastStallSig
753
+
754
+ // Commit this pass's inputs BEFORE running the blocks so state written
755
+ // mid-pass (which queues a follow-up pass) diffs against what this pass
756
+ // already handled — like React committing the render before its effects.
757
+ this.lastConnected = connected
758
+ this.lastOtaStatus = otaStatus
759
+ this.lastOtaProgress = otaProgress
760
+ this.lastBuildNumber = buildNumber
761
+ this.lastDisplayState = displayState
762
+ this.lastStallSig = stallSig
763
+
764
+ if (this.prevDisplayState !== displayState) {
765
+ console.log(
766
+ `[OTA_PROGRESS] displayState ${this.prevDisplayState ?? "<init>"} -> ${displayState}`,
767
+ JSON.stringify({
768
+ connected,
769
+ sawReconnectEdge: this.sawReconnectEdge,
770
+ errorMsg: this.errorMsg || null,
771
+ otaStatus: otaStatus
772
+ ? {
773
+ stepType: otaStatus.stepType,
774
+ phase: otaStatus.phase,
775
+ status: otaStatus.status,
776
+ step: `${otaStatus.currentStep}/${otaStatus.totalSteps}`,
777
+ pct: otaStatus.overallPercent,
778
+ }
779
+ : null,
780
+ otaProgress: otaProgress
781
+ ? {
782
+ currentUpdate: otaProgress.currentUpdate,
783
+ stage: otaProgress.stage,
784
+ status: otaProgress.status,
785
+ progress: otaProgress.progress,
786
+ }
787
+ : null,
788
+ }),
789
+ )
790
+ this.prevDisplayState = displayState
791
+ }
792
+
793
+ // APK-step latch (was the [otaStatus] effect above the timers).
794
+ if (otaStatusChanged && otaStatus?.stepType === "apk") {
795
+ this.apkStepSeen = true
796
+ }
797
+
798
+ // Legacy MTK completion side effect (WP 8C-a): old builds never send the
799
+ // mtk_update_complete BLE event; their install FINISHED is the completion signal,
800
+ // and the legacy screen marked MTK as updated this session right there.
801
+ if (
802
+ otaStatusChanged &&
803
+ isLegacyShapedOtaStatus(otaStatus) &&
804
+ otaStatus?.stepType === "mtk" &&
805
+ otaStatus.phase === "install" &&
806
+ otaStatus.status === "complete"
807
+ ) {
808
+ console.log("[OTA_PROGRESS] legacy mtk install complete — marking MTK updated this session")
809
+ useGlassesStore.getState().setMtkUpdatedThisSession(true)
810
+ }
811
+
812
+ // Version-change (downgrade) install started: once ASG reports the apk install
813
+ // phase, the handoff to the recovery worker has happened. From here the phone
814
+ // stops driving ota_start (see runConnectEdge) and waits for exact-version
815
+ // convergence — the wipe means no resumable ASG session and no build increase.
816
+ if (
817
+ this.versionChangeSession &&
818
+ !this.versionChangeInstallStarted &&
819
+ // Only latch from a status that belongs to THIS session: an install/apk status is a
820
+ // response to our own acknowledged ota_start, so a leftover from a prior session
821
+ // sitting in the store at mount cannot prematurely enter the detour wait (which would
822
+ // suppress ota_start and hang the fresh downgrade until the global timeout).
823
+ this.otaStartOwnership?.outcome === "acknowledged" &&
824
+ otaStatus?.stepType === "apk" &&
825
+ otaStatus.phase === "install"
826
+ ) {
827
+ console.log("[OTA_PROGRESS] version-change: apk install started — entering detour wait")
828
+ this.versionChangeInstallStarted = true
829
+ this.emitInternalChange()
830
+ }
831
+
832
+ // Ownership refuted: ASG emits install/STARTED BEFORE handing off, so the latch above can
833
+ // be set even when no transaction ends up owning the detour. Recovery answers every
834
+ // handoff synchronously, so exactly three error codes prove non-ownership and release the
835
+ // latch: downgrade_handoff_refused (recovery's explicit verdict),
836
+ // downgrade_handoff_failed (no verdict at all — recovery dead/missing, so it never began),
837
+ // and downgrade_transaction_stalled (the long-stop past recovery's own stale give-up).
838
+ // An accepted-but-slow transaction emits NONE of these (acceptance cancels the short
839
+ // watchdog), so the latch is never released while a live worker owns the staged artifact
840
+ // — which recovery additionally claims by rename at acceptance.
841
+ if (
842
+ this.versionChangeInstallStarted &&
843
+ !this.versionChangeConverged &&
844
+ otaStatus?.status === "failed" &&
845
+ (otaStatus.error === "downgrade_handoff_refused" ||
846
+ otaStatus.error === "downgrade_handoff_failed" ||
847
+ otaStatus.error === "downgrade_transaction_stalled")
848
+ ) {
849
+ console.log(
850
+ `[OTA_PROGRESS] version-change: no transaction owns the detour (${otaStatus.error}) — releasing latch`,
851
+ )
852
+ this.versionChangeInstallStarted = false
853
+ this.emitInternalChange()
854
+ }
855
+
856
+ // Version-change convergence: the reconnected glasses report exactly the pinned
857
+ // target. This is the ONLY completion signal for a downgrade (lower build, no
858
+ // increase, wiped session). Mirrors the legacy build-increase latch but by equality.
859
+ if (
860
+ this.versionChangeSession &&
861
+ this.versionChangeInstallStarted &&
862
+ !this.versionChangeConverged &&
863
+ this.versionChangeTarget !== null &&
864
+ buildNumberChanged &&
865
+ buildNumber
866
+ ) {
867
+ const currentBuild = Number.parseInt(buildNumber, 10)
868
+ if (Number.isFinite(currentBuild) && currentBuild === this.versionChangeTarget) {
869
+ console.log(`[OTA_PROGRESS] version-change converged: glasses report target ${currentBuild}, completing`)
870
+ this.clearPerStepTimers()
871
+ this.clearGlobalTimeout()
872
+ this.apkStepSeen = true
873
+ this.setErrorMsg("")
874
+ this.versionChangeConverged = true
875
+ this.emitInternalChange()
876
+ }
877
+ }
878
+
879
+ // Legacy APK completion by build-number increase (WP 8C-c): old builds reboot into
880
+ // the new build without a reliable explicit reconnect status; the fresh version_info
881
+ // build number is the completion signal (ported from progress-legacy.tsx, which
882
+ // completed from installing-with-apk and from starting/failed).
883
+ if (
884
+ buildNumberChanged &&
885
+ !this.apkCompletedViaBuildIncrease &&
886
+ this.apkExpectedInSession &&
887
+ legacySession &&
888
+ this.initialBuildNumber
889
+ ) {
890
+ const currentBuild = Number.parseInt(buildNumber, 10)
891
+ const initialBuild = Number.parseInt(this.initialBuildNumber, 10)
892
+ const increased = Number.isFinite(currentBuild) && Number.isFinite(initialBuild) && currentBuild > initialBuild
893
+ const apkStepCurrent = otaStatus?.stepType === "apk" || otaProgress?.currentUpdate === "apk"
894
+ const detectable =
895
+ displayState === "starting" || displayState === "failed" || (displayState === "updating" && apkStepCurrent)
896
+ if (increased && detectable) {
897
+ console.log(
898
+ `[OTA_PROGRESS] legacy apk complete via build-number increase (${initialBuild} -> ${currentBuild}), completing session`,
899
+ )
900
+ this.clearPerStepTimers()
901
+ this.clearLegacyApkSettleTimer()
902
+ this.setLegacyApkSettleHold(false)
903
+ this.apkStepSeen = true
904
+ this.setErrorMsg("")
905
+ this.setApkCompletedViaBuildIncrease(true)
906
+ }
907
+ }
908
+
909
+ // Cancel the query-reply fallback as soon as the glasses reply with a useful
910
+ // status (in_progress / step_complete / complete / failed) or any progress
911
+ // event. An "idle" ota_status means glasses have no active session, so we
912
+ // must keep the fallback armed and let it retry ota_start.
913
+ if (otaStatusChanged || otaProgressChanged) {
914
+ if (this.queryReplyTimeout && hasRecoveringOtaReply(otaStatus, otaProgress)) {
915
+ this.clearQueryReplyTimeout()
916
+ }
917
+ }
918
+
919
+ /**
920
+ * Connect-edge — the only place that decides to send ota_start /
921
+ * ota_query_status and flips sawReconnectEdge
922
+ * on the false -> true transition that signals the BES reboot completed.
923
+ */
924
+ if (connectedChanged) {
925
+ this.runConnectEdge(connected)
926
+ }
927
+
928
+ if (displayStateChanged && isTerminalForWatchdog(displayState)) {
929
+ this.clearGlobalTimeout()
930
+ }
931
+
932
+ if (isMount) {
933
+ GlobalEventEmitter.on("ota_start_ack", this.handleAck)
934
+ GlobalEventEmitter.on("mtk_update_complete", this.handleMtkComplete)
935
+ GlobalEventEmitter.on("glasses_session_changed", this.handleGlassesSessionChanged)
936
+ }
937
+
938
+ // Ping keepalive while an OTA is actively running (legacy sessions pinged on the
939
+ // padded interval — old builds slept less eagerly and pings are costlier for them).
940
+ if (connectedChanged || displayStateChanged) {
941
+ this.clearPingInterval()
942
+ const active =
943
+ connected && (displayState === "starting" || displayState === "updating") && !this.isInVersionChangeDetour()
944
+ if (active) {
945
+ void BluetoothSdk.ping().catch(() => {})
946
+ const pingIntervalMs = legacySession ? LEGACY_PING_INTERVAL_MS : PING_INTERVAL_MS
947
+ this.pingInterval = setInterval(() => {
948
+ void BluetoothSdk.ping().catch(() => {})
949
+ this.maybePollApkInstallStatus()
950
+ }, pingIntervalMs)
951
+ }
952
+ }
953
+
954
+ // Any glasses activity proves the logical ota_start was accepted.
955
+ if ((otaStatusChanged || otaProgressChanged) && (otaStatus || otaProgress)) {
956
+ this.onFirstActivity()
957
+ }
958
+
959
+ // The stuck-at-zero watchdog clears only on the FIRST real (>0%) progress.
960
+ // Without this distinction, an ota_status reply with stepPercent=0 would
961
+ // disable the watchdog before any download had actually started, hiding
962
+ // wedged downloads from the user.
963
+ if (otaStatusChanged || otaProgressChanged) {
964
+ const stepPct = otaStatus?.stepPercent ?? 0
965
+ const overallPct = otaStatus?.overallPercent ?? 0
966
+ const legacyPct = otaProgress?.progress ?? 0
967
+ if (stepPct > 0 || overallPct > 0 || legacyPct > 0) {
968
+ this.onFirstNonZeroProgress()
969
+ }
970
+ }
971
+
972
+ // Progress stall watchdog — fails the update if glasses go silent mid-step.
973
+ // Keyed on the staleness SIGNATURE string (not the object refs) so a store
974
+ // update that doesn't change the signature keeps the same timer running
975
+ // instead of forever re-extending the deadline.
976
+ if (stallSigChanged || displayStateChanged) {
977
+ if (this.isInVersionChangeDetour()) {
978
+ // Recovery worker owns the transaction; there are no progress events to stall on.
979
+ this.clearProgressTimeout()
980
+ } else if (displayState !== "starting" && displayState !== "updating") {
981
+ this.clearProgressTimeout()
982
+ } else if (!stallSig) {
983
+ this.clearProgressTimeout()
984
+ } else {
985
+ const duration = stallDuration
986
+ this.clearProgressTimeout()
987
+ this.progressTimeout = setTimeout(() => {
988
+ this.progressTimeout = null
989
+ if (isTerminalForWatchdog(this.computeDisplayStateNow())) return
990
+ console.log(`[OTA_PROGRESS] watchdog: progress stalled for ${duration}ms, failing`)
991
+ this.setErrorMsg(OtaProgressMessages.stalledOrStuck)
992
+ }, duration)
993
+ }
994
+ }
995
+
996
+ // Lockout on Continue button after BES restart to prevent accidental tap
997
+ // (15s unified, 35s for legacy-shaped sessions — WP 8C-f).
998
+ if (displayStateChanged && displayState === "restarting") {
999
+ this.clearContinueLockoutTimer()
1000
+ const lockoutMs = legacySession ? LEGACY_BES_CONTINUE_LOCKOUT_MS : BES_CONTINUE_LOCKOUT_MS
1001
+ this.setContinueButtonDisabled(true)
1002
+ this.continueLockoutTimer = setTimeout(() => {
1003
+ this.continueLockoutTimer = null
1004
+ this.setContinueButtonDisabled(false)
1005
+ }, lockoutMs)
1006
+ }
1007
+
1008
+ // Legacy MTK install stall simulation (WP 8C-e): display-only. The simulation
1009
+ // timers only ever touch the projected snapshot percent — they never feed back
1010
+ // into arbitration (no reaction pass, no watchdog resets).
1011
+ if (otaProgressChanged) {
1012
+ this.runMtkStallSimulationPass(otaProgress, legacySession)
1013
+ }
1014
+
1015
+ // Clear the selected update once this session reaches any terminal UI state.
1016
+ // Catches paths the MantleManager ota_status listener misses — notably BES success
1017
+ // (which terminates as `step_complete`, not `complete`) and APK build-number fallback
1018
+ // completions (which never produce a `complete` ota_status).
1019
+ if (
1020
+ displayStateChanged &&
1021
+ (displayState === "complete" || displayState === "restarting" || displayState === "failed")
1022
+ ) {
1023
+ if (displayState === "failed") {
1024
+ this.clearPerStepTimers()
1025
+ }
1026
+ useGlassesStore.getState().setOtaUpdateAvailable(null)
1027
+ if (displayState === "complete" || displayState === "failed") {
1028
+ void this.teardownHotspotTransport()
1029
+ }
1030
+ }
1031
+ }
1032
+
1033
+ /**
1034
+ * Reconnect arbitration shared by the physical BLE connect edge and the
1035
+ * glasses_session_changed signal. The latter exists because the BES keeps the BLE
1036
+ * link alive across asg_client restarts (APK OTA): no physical edge ever fires, so
1037
+ * the changed process session id is the only reconnect signal the coordinator gets
1038
+ * (incident rep_01KY6BJ0B7A4RBMQ7VN39KAE5E). Always returns true (the caller's
1039
+ * edge handling is complete).
1040
+ */
1041
+ private runReconnectArbitration(label: string): boolean {
1042
+ console.log(`[OTA_PROGRESS] ${label}: false->true, flipping sawReconnectEdge=true`)
1043
+ // A physical reconnect passed through the disconnect branch first, which cleared
1044
+ // these timers; a session-change edge never disconnects, so a fallback armed by an
1045
+ // earlier mount/query could otherwise fire alongside the arbitration below and send
1046
+ // a duplicate ota_start. Clearing here is a no-op on the physical path.
1047
+ this.clearQueryReplyTimeout()
1048
+ this.setSawReconnectEdge(true)
1049
+
1050
+ if (this.besRestartDisconnectObserved) {
1051
+ console.log(`[OTA_PROGRESS] ${label}: BES reboot reconnect observed — no ota_query_status`)
1052
+ this.completeBesRestartRecovery()
1053
+ return true
1054
+ }
1055
+
1056
+ // Legacy apk settle window (WP 8C): the glasses restarting into the new build is
1057
+ // the EXPECTED reconnect here — the legacy screen took no action on it. Skip the
1058
+ // query/fallback; the settle timer (or the build-number increase) completes.
1059
+ if (this.legacyApkSettleHold) {
1060
+ console.log(`[OTA_PROGRESS] ${label}: reconnect during legacy apk settle hold — no query`)
1061
+ return true
1062
+ }
1063
+
1064
+ console.log(`[OTA_PROGRESS] ${label}: reconnected, sending ota_query_status`)
1065
+ if (this.otaStartOwnership?.outcome === "pending") {
1066
+ void this.sendOtaStartWithWatchdogs()
1067
+ }
1068
+ void BluetoothSdk.sendOtaQueryStatus()
1069
+ this.armQueryReplyFallback("reconnect")
1070
+ return true
1071
+ }
1072
+
1073
+ private runConnectEdge(connected: boolean): void {
1074
+ const prev = this.prevConnected
1075
+ this.prevConnected = connected
1076
+
1077
+ if (!connected) {
1078
+ console.log("[OTA_PROGRESS] connect-edge: disconnected")
1079
+ // Also drop pending query/retry sends: firing ota_start into a dead link
1080
+ // can only produce a false "failed" state. Reconnect arbitration first
1081
+ // queries durable status and re-arms the one permitted fallback.
1082
+ this.clearQueryReplyTimeout()
1083
+ this.clearRetryTimeout()
1084
+ return
1085
+ }
1086
+
1087
+ const becameConnected = prev === false && connected === true
1088
+ if (becameConnected && this.runReconnectArbitration("connect-edge")) {
1089
+ return
1090
+ }
1091
+
1092
+ // Version-change detour: after the install started, EVERY reconnect is expected
1093
+ // (the passive factory build, then the target). Do NOT send ota_start/query — the
1094
+ // factory build would treat the pin as a plain upgrade and start a redundant
1095
+ // download racing the recovery worker. Convergence (buildNumber === target) is
1096
+ // detected in the reaction pass; here we simply take no action.
1097
+ if (this.versionChangeSession && this.versionChangeInstallStarted && !this.versionChangeConverged) {
1098
+ console.log("[OTA_PROGRESS] connect-edge: reconnect during version-change detour — no query (expected)")
1099
+ return
1100
+ }
1101
+
1102
+ const storeState = useGlassesStore.getState()
1103
+
1104
+ // Initial mount (prev === current === true). If no session yet, kick off ota_start.
1105
+ // An idle ota_status has an empty sessionId; treat it as no session so ota_start
1106
+ // still begins an explicit install.
1107
+ const isIdleStatus = !!storeState.otaStatus && storeState.otaStatus.sessionId === ""
1108
+ const noSessionYet = (!storeState.otaStatus && !storeState.otaProgress) || isIdleStatus
1109
+ if (noSessionYet) {
1110
+ if (!isIdleStatus && this.otaStartOwnership?.outcome === "acknowledged") {
1111
+ console.log("[OTA_PROGRESS] initial mount, acknowledged session has no cached status — reconciling")
1112
+ void BluetoothSdk.sendOtaQueryStatus().catch(() => {})
1113
+ this.armQueryReplyFallback("initial-mount")
1114
+ return
1115
+ }
1116
+ console.log(
1117
+ isIdleStatus
1118
+ ? "[OTA_PROGRESS] initial mount, idle status (no sessionId), sending ota_start"
1119
+ : "[OTA_PROGRESS] initial mount, no session in store, sending ota_start",
1120
+ )
1121
+ this.retryCount = 0
1122
+ if (isIdleStatus && this.otaStartOwnership?.outcome !== "pending" && !this.prepareFreshOtaStartAttempt()) {
1123
+ return
1124
+ }
1125
+ void this.sendOtaStartWithWatchdogs()
1126
+ } else {
1127
+ console.log("[OTA_PROGRESS] initial mount, session exists, sending ota_query_status")
1128
+ void BluetoothSdk.sendOtaQueryStatus()
1129
+ this.armQueryReplyFallback("initial-mount")
1130
+ }
1131
+ }
1132
+
1133
+ // --- BLE OTA event listeners (engine GlobalEventEmitter, fed by OtaService) ---
1134
+
1135
+ private readonly handleAck = (): void => {
1136
+ const ownership = this.otaStartOwnership
1137
+ if (!ownership || ownership.outcome === "rejected") return
1138
+ if (ownership.outcome === "acknowledged") return
1139
+ console.log("[OTA_PROGRESS] ota_start_ack received")
1140
+ ownership.outcome = "acknowledged"
1141
+ this.clearRetryTimeout()
1142
+ }
1143
+
1144
+ /**
1145
+ * The asg process restarted while the BES kept the BLE link up (sid change observed
1146
+ * by the bridge): treat it exactly like a physical reconnect edge.
1147
+ */
1148
+ private readonly handleGlassesSessionChanged = (): void => {
1149
+ this.runReconnectArbitration("session-change")
1150
+ }
1151
+
1152
+ private readonly handleMtkComplete = (): void => {
1153
+ console.log("[OTA_PROGRESS] mtk_update_complete received")
1154
+ this.clearProgressTimeout()
1155
+ this.onFirstActivity()
1156
+ this.onFirstNonZeroProgress()
1157
+ void BluetoothSdk.sendOtaQueryStatus()
1158
+ useGlassesStore.getState().setMtkUpdatedThisSession(true)
1159
+ }
1160
+
1161
+ // --- legacy (< 37) compatibility, ported from progress-legacy.tsx (WP 8C) ---
1162
+
1163
+ /**
1164
+ * Reacts to a NEW legacy-shaped install-phase "complete" (an old build's
1165
+ * `install FINISHED`). Two ports from the legacy screen:
1166
+ * - last-write-wins: completion arriving after a watchdog failure overrides it
1167
+ * (the old screen completed from "failed" too);
1168
+ * - apk settle window: an apk install FINISHED observed in flight is held out of
1169
+ * "complete" for LEGACY_APK_COMPLETION_SETTLE_MS so the ASG process restart
1170
+ * settles (and fresh version_info lands) before the user can continue. From
1171
+ * starting/failed (post-reboot explicit signal) completion is immediate.
1172
+ */
1173
+ private handleLegacyInstallCompleteEdge(otaStatus: OtaStatus | null): void {
1174
+ if (!isLegacyShapedOtaStatus(otaStatus) || otaStatus?.phase !== "install" || otaStatus.status !== "complete") {
1175
+ return
1176
+ }
1177
+
1178
+ if (this.errorMsg) {
1179
+ console.log(
1180
+ "[OTA_PROGRESS] legacy install complete arrived after a failure — completing (legacy last-write-wins)",
1181
+ )
1182
+ this.setErrorMsg("")
1183
+ this.clearLegacyApkSettleTimer()
1184
+ this.setLegacyApkSettleHold(false)
1185
+ return
1186
+ }
1187
+
1188
+ if (
1189
+ otaStatus.stepType === "apk" &&
1190
+ !this.apkCompletedViaBuildIncrease &&
1191
+ !this.legacyApkSettleHold &&
1192
+ this.legacyApkSettleTimer === null &&
1193
+ this.lastDisplayState === "updating"
1194
+ ) {
1195
+ console.log(
1196
+ `[OTA_PROGRESS] legacy apk install complete — holding complete for ${LEGACY_APK_COMPLETION_SETTLE_MS}ms settle window`,
1197
+ )
1198
+ this.setLegacyApkSettleHold(true)
1199
+ this.legacyApkSettleTimer = setTimeout(() => {
1200
+ this.legacyApkSettleTimer = null
1201
+ console.log("[OTA_PROGRESS] legacy apk settle window elapsed — releasing complete")
1202
+ this.setLegacyApkSettleHold(false)
1203
+ }, LEGACY_APK_COMPLETION_SETTLE_MS)
1204
+ }
1205
+ }
1206
+
1207
+ private isBesRestartExpected(
1208
+ otaStatus: OtaStatus | null,
1209
+ otaProgress: OtaProgress | null,
1210
+ legacySession: boolean,
1211
+ ): boolean {
1212
+ const statusReadyToRestart =
1213
+ otaStatus?.stepType === "bes" &&
1214
+ otaStatus.phase === "install" &&
1215
+ (otaStatus.status === "step_complete" || otaStatus.status === "complete")
1216
+ const progressReadyToRestart =
1217
+ legacySession &&
1218
+ otaProgress?.currentUpdate === "bes" &&
1219
+ otaProgress.stage === "install" &&
1220
+ otaProgress.status === "FINISHED"
1221
+
1222
+ // progress-legacy treated a BES disconnect during active installation as the
1223
+ // expected power cycle even when its terminal FINISHED event was lost. Keep
1224
+ // that fallback only for the sticky pre-37 profile selected when the flow began.
1225
+ const legacyInstallInProgress =
1226
+ legacySession &&
1227
+ ((otaStatus?.stepType === "bes" && otaStatus.phase === "install" && otaStatus.status === "in_progress") ||
1228
+ (otaProgress?.currentUpdate === "bes" &&
1229
+ otaProgress.stage === "install" &&
1230
+ (otaProgress.status === "STARTED" || otaProgress.status === "PROGRESS")))
1231
+ return statusReadyToRestart || progressReadyToRestart || legacyInstallInProgress
1232
+ }
1233
+
1234
+ /** Complete only after a protocol-authorized BES restart traverses disconnect -> reconnect. */
1235
+ private completeBesRestartRecovery(): void {
1236
+ if (!this.besRestartDisconnectObserved || this.besRestartRecovery !== "awaiting") return
1237
+ console.log("[OTA_PROGRESS] BES restart reconnected — completing by reboot edge")
1238
+ this.clearPerStepTimers()
1239
+ this.setErrorMsg("")
1240
+ this.besRestartRecovery = "complete"
1241
+ this.emitInternalChange()
1242
+ }
1243
+
1244
+ /**
1245
+ * Legacy MTK install stall simulation (WP 8C-e), ported verbatim: the MTK system
1246
+ * install typically stalls around 49-50%. Every real event restarts stall detection;
1247
+ * after LEGACY_MTK_STALL_DETECT_MS of silence inside the [45, 55) zone, simulate +1%
1248
+ * every LEGACY_MTK_SIM_TICK_MS from max(stall+1, 51), capped at 60. Real progress
1249
+ * beyond the simulated value clears it. Display-only by construction: the timers call
1250
+ * only {@link setMtkSimulatedPercent}, which never queues a reaction pass.
1251
+ */
1252
+ private runMtkStallSimulationPass(otaProgress: OtaProgress | null, legacySession: boolean): void {
1253
+ const isLegacyMtkInstall =
1254
+ legacySession &&
1255
+ otaProgress?.currentUpdate === "mtk" &&
1256
+ otaProgress.stage === "install" &&
1257
+ (otaProgress.status === "STARTED" || otaProgress.status === "PROGRESS")
1258
+
1259
+ if (!isLegacyMtkInstall) {
1260
+ this.clearMtkSimulationTimers()
1261
+ this.setMtkSimulatedPercent(null)
1262
+ return
1263
+ }
1264
+
1265
+ const realProgress = otaProgress.progress ?? 0
1266
+ if (realProgress > 0 && realProgress !== this.lastRealMtkProgress) {
1267
+ this.lastRealMtkProgress = realProgress
1268
+ if (this.mtkSimulatedPercent !== null && realProgress > this.mtkSimulatedPercent) {
1269
+ this.setMtkSimulatedPercent(null)
1270
+ }
1271
+ }
1272
+
1273
+ // A real event arrived: restart stall detection (and stop any running ticker).
1274
+ this.clearMtkSimulationTimers()
1275
+ const inStallZone =
1276
+ realProgress >= LEGACY_MTK_STALL_ZONE_MIN_PERCENT && realProgress < LEGACY_MTK_STALL_ZONE_MAX_PERCENT
1277
+ if (!inStallZone) return
1278
+
1279
+ this.mtkStallDetectTimer = setTimeout(() => {
1280
+ this.mtkStallDetectTimer = null
1281
+ const stalledAt = this.lastRealMtkProgress
1282
+ const prev = this.mtkSimulatedPercent
1283
+ const target = Math.max(
1284
+ prev !== null ? Math.max(prev, stalledAt + 1) : stalledAt + 1,
1285
+ LEGACY_MTK_SIM_FLOOR_PERCENT,
1286
+ )
1287
+ console.log(
1288
+ `[OTA_PROGRESS] legacy mtk install stalled at ${stalledAt}%, simulating display progress from ${target}%`,
1289
+ )
1290
+ this.setMtkSimulatedPercent(target)
1291
+ this.mtkSimTickTimer = setInterval(() => {
1292
+ const current = this.mtkSimulatedPercent ?? stalledAt + 1
1293
+ const capped = Math.min(current + 1, LEGACY_MTK_SIM_CAP_PERCENT)
1294
+ this.setMtkSimulatedPercent(capped)
1295
+ if (capped >= LEGACY_MTK_SIM_CAP_PERCENT && this.mtkSimTickTimer) {
1296
+ clearInterval(this.mtkSimTickTimer)
1297
+ this.mtkSimTickTimer = null
1298
+ }
1299
+ }, LEGACY_MTK_SIM_TICK_MS)
1300
+ }, LEGACY_MTK_STALL_DETECT_MS)
1301
+ }
1302
+
1303
+ // --- watchdogs / send path ---
1304
+
1305
+ /**
1306
+ * Read the current derived display state from the store + internal state
1307
+ * synchronously — used inside setTimeout callbacks.
1308
+ */
1309
+ private computeDisplayStateNow(): DisplayState {
1310
+ const state = useGlassesStore.getState()
1311
+ return deriveDisplayState({
1312
+ otaStatus: state.otaStatus,
1313
+ otaProgress: state.otaProgress,
1314
+ connected: isGlassesConnected(state.connection),
1315
+ errorMsg: this.errorMsg,
1316
+ sawReconnectEdge: this.sawReconnectEdge,
1317
+ legacyApkSettleHold: this.legacyApkSettleHold,
1318
+ apkCompletedViaBuildIncrease: this.apkCompletedViaBuildIncrease,
1319
+ besRestartRecovery: this.besRestartRecovery,
1320
+ versionChangeConverged: this.versionChangeConverged,
1321
+ versionChangeSession: this.versionChangeSession,
1322
+ })
1323
+ }
1324
+
1325
+ /**
1326
+ * During an APK install the package installer kills and restarts the glasses
1327
+ * process, and the new process's completion push can lose its startup race
1328
+ * against the UART transport (incident rep_01KY31HEMTSBSMK8DVMNXJ5XGG: the
1329
+ * phone sat on a stale "install in_progress 0%" until the stall watchdog
1330
+ * failed a successful update). The persisted session on the glasses always
1331
+ * knows the truth, so poll ota_query_status on the keepalive tick while an
1332
+ * apk step sits in its install phase — the reply either confirms progress or
1333
+ * delivers the missed completion. Gated to the apk install phase so a stray
1334
+ * idle reply can't disturb an active download; legacy (< 37) builds ignore
1335
+ * the query, which is harmless.
1336
+ *
1337
+ * The native query pends up to 15s and rejects a concurrent call with
1338
+ * request_in_flight — longer than the 10s tick — so exactly one poll is kept
1339
+ * in flight (the glasses being slow to answer IS the silent-restart window
1340
+ * this poll exists for) and rejections are swallowed: the next tick retries.
1341
+ */
1342
+ private maybePollApkInstallStatus(): void {
1343
+ if (this.isInVersionChangeDetour()) return
1344
+ if (this.apkInstallPollInFlight) return
1345
+ const s = useGlassesStore.getState().otaStatus
1346
+ if (s?.stepType === "apk" && s.phase === "install" && s.status === "in_progress") {
1347
+ this.apkInstallPollInFlight = true
1348
+ void BluetoothSdk.sendOtaQueryStatus()
1349
+ .catch(() => {})
1350
+ .finally(() => {
1351
+ this.apkInstallPollInFlight = false
1352
+ })
1353
+ }
1354
+ }
1355
+
1356
+ /**
1357
+ * "Glasses are talking to us" — suppresses retry if the native request later rejects.
1358
+ * Important: this does NOT clear the stuck-at-zero watchdog; that one fires
1359
+ * on real progress > 0% (see {@link onFirstNonZeroProgress}).
1360
+ */
1361
+ private onFirstActivity(): void {
1362
+ if (this.hasFirstActivity) return
1363
+ this.hasFirstActivity = true
1364
+ if (this.otaStartOwnership?.outcome === "pending") {
1365
+ this.otaStartOwnership.outcome = "acknowledged"
1366
+ }
1367
+ this.clearRetryTimeout()
1368
+ }
1369
+
1370
+ /**
1371
+ * "Real download progress arrived" — clears the stuck-at-zero watchdog. We
1372
+ * deliberately wait for non-zero progress before clearing this so that an
1373
+ * ota_status reply with stepPercent: 0 (which is "first activity" but not
1374
+ * "real progress") doesn't disable the only watchdog that catches a wedged
1375
+ * download.
1376
+ */
1377
+ private onFirstNonZeroProgress(): void {
1378
+ if (this.hasFirstNonZeroProgress) return
1379
+ this.hasFirstNonZeroProgress = true
1380
+ this.clearStuckTimeout()
1381
+ }
1382
+
1383
+ private maybeStartGlobalTimeout(): void {
1384
+ if (this.globalTimeoutStarted) return
1385
+ this.globalTimeoutStarted = true
1386
+ // Legacy-shape check happens once, when the session cap is armed at the first
1387
+ // ota_start send (WP 8C-b): old builds get the padded legacy cap for the whole
1388
+ // session; a session that starts unified keeps the unified cap.
1389
+ const globalTimeoutMs =
1390
+ this.versionChangeSession || this.isLegacySessionShapeNow() ? LEGACY_GLOBAL_OTA_TIMEOUT_MS : GLOBAL_OTA_TIMEOUT_MS
1391
+ this.globalTimeout = setTimeout(() => {
1392
+ this.globalTimeout = null
1393
+ this.globalTimeoutStarted = false
1394
+ if (isTerminalForWatchdog(this.computeDisplayStateNow())) return
1395
+ console.log("[OTA_PROGRESS] watchdog: global timeout fired, failing session")
1396
+ this.clearPerStepTimers()
1397
+ // Legacy parity: the old screen's global-timeout handler also killed the apk
1398
+ // completion timer and the MTK stall simulation (the flags/percent stay put —
1399
+ // errorMsg outranks them in the projection, and retry()/last-write-wins reset them).
1400
+ this.clearLegacyApkSettleTimer()
1401
+ this.clearMtkSimulationTimers()
1402
+ this.setErrorMsg(OtaProgressMessages.globalTimeout)
1403
+ }, globalTimeoutMs)
1404
+ }
1405
+
1406
+ private armStuckWatchdogOnly(): void {
1407
+ this.clearStuckTimeout()
1408
+
1409
+ // Durations resolved at arm time (WP 8C-b): legacy-shaped sessions get the
1410
+ // padded values progress-legacy.tsx used for old (< 37) builds.
1411
+ const legacySession = this.isLegacySessionShapeNow()
1412
+ const stuckTimeoutMs = legacySession ? LEGACY_DOWNLOAD_STUCK_TIMEOUT_MS : DOWNLOAD_STUCK_TIMEOUT_MS
1413
+
1414
+ this.stuckTimeout = setTimeout(() => {
1415
+ this.stuckTimeout = null
1416
+ const d = this.computeDisplayStateNow()
1417
+ const state = useGlassesStore.getState()
1418
+ if (d !== "starting" && !(d === "updating" && state.otaStatus?.phase === "download")) {
1419
+ return
1420
+ }
1421
+ const pct = latestPercentForStuck(state.otaStatus, state.otaProgress)
1422
+ if (pct !== 0) return
1423
+ console.log(`[OTA_PROGRESS] watchdog: stuck at 0% for ${stuckTimeoutMs}ms, failing`)
1424
+ this.clearRetryTimeout()
1425
+ this.setErrorMsg(OtaProgressMessages.stalledOrStuck)
1426
+ }, stuckTimeoutMs)
1427
+ }
1428
+
1429
+ /** Arm the 0%-stuck watchdog only when this logical session has none. */
1430
+ private maybeArmStuckWatchdog(): void {
1431
+ if (this.stuckTimeout || this.hasFirstNonZeroProgress) return
1432
+ this.armStuckWatchdogOnly()
1433
+ }
1434
+
1435
+ private sendOtaStartWithWatchdogs(): Promise<void> {
1436
+ // INVARIANT BACKSTOP — not normal control flow. Every entry point that can drive the
1437
+ // glasses (connect-edge, query fallback, retry, mount) carries its own detour gate with the
1438
+ // right behavior for that site; this final check only exists so that a FUTURE entry point
1439
+ // that forgets its gate degrades to a refused send + loud log instead of a corrupted
1440
+ // transaction (an ota_start mid-detour starts a parallel install on the factory build and
1441
+ // can trigger a second handoff). If this ever fires, a missed site gate exists — fix it.
1442
+ if (this.isInVersionChangeDetour()) {
1443
+ console.error(
1444
+ "[OTA_PROGRESS] BACKSTOP: refused ota_start during a latched version-change detour — a caller is missing its detour gate",
1445
+ )
1446
+ return Promise.resolve()
1447
+ }
1448
+ const existingOwnership = this.otaStartOwnership
1449
+ if (existingOwnership?.outcome === "pending") {
1450
+ this.maybeStartGlobalTimeout()
1451
+ if (this.selectedTransport === "wifi" || this.hotspotManifestUrl) this.maybeArmStuckWatchdog()
1452
+ console.log("[OTA_PROGRESS] ota_start already pending — adopting existing native request")
1453
+ return existingOwnership.promise
1454
+ }
1455
+ if (existingOwnership?.outcome === "acknowledged") {
1456
+ this.maybeStartGlobalTimeout()
1457
+ this.maybeArmStuckWatchdog()
1458
+ console.log("[OTA_PROGRESS] ota_start already acknowledged — refusing duplicate start")
1459
+ return Promise.resolve()
1460
+ }
1461
+ if (existingOwnership?.outcome === "rejected" && !this.prepareFreshOtaStartAttempt()) {
1462
+ return Promise.resolve()
1463
+ }
1464
+ this.maybeStartGlobalTimeout()
1465
+ if (this.selectedTransport === "wifi" || this.hotspotManifestUrl) this.maybeArmStuckWatchdog()
1466
+
1467
+ const ownership: OtaStartOwnership = {
1468
+ outcome: "pending",
1469
+ promise: Promise.resolve(),
1470
+ }
1471
+ ownership.promise = this.performOtaStart(ownership)
1472
+ this.otaStartOwnership = ownership
1473
+ return ownership.promise
1474
+ }
1475
+
1476
+ private async performOtaStart(ownership: OtaStartOwnership): Promise<void> {
1477
+ let nativeStartAttempted = false
1478
+ try {
1479
+ const state = useGlassesStore.getState()
1480
+ let otaVersionUrl = resolveOtaManifestUrl(state.otaVersionUrl, state.buildNumber)
1481
+ if (this.selectedTransport === "hotspot") {
1482
+ if (!this.hotspotManifestUrl) {
1483
+ if (!this.preparedCheckResult) {
1484
+ throw new Error("No selected OTA check is available for hotspot staging")
1485
+ }
1486
+ this.hotspotManifestUrl = await hotspotOtaTransport.prepare(this.preparedCheckResult, (progress) => {
1487
+ this.hotspotPhase = progress.phase
1488
+ this.hotspotArtifactPercent = progress.artifact?.artifactPercent ?? null
1489
+ this.emitInternalChange()
1490
+ })
1491
+ }
1492
+ otaVersionUrl = this.hotspotManifestUrl
1493
+ this.maybeArmStuckWatchdog()
1494
+ }
1495
+ console.log(`[OTA_PROGRESS] sending ota_start with ${this.selectedTransport} manifest URL: ${otaVersionUrl}`)
1496
+ nativeStartAttempted = true
1497
+ await BluetoothSdk.startOtaUpdate(otaVersionUrl)
1498
+ if (this.otaStartOwnership !== ownership) return
1499
+ // The public SDK promise resolves only from ota_start_ack. Treat that
1500
+ // resolution as the acknowledgement even if the parallel event dispatch
1501
+ // reaches the coordinator later (or is dropped by a remount).
1502
+ this.handleAck()
1503
+ } catch (err) {
1504
+ console.warn("[OTA_PROGRESS] sendOtaStart threw", err)
1505
+
1506
+ if (this.otaStartOwnership !== ownership) return
1507
+
1508
+ if (this.selectedTransport === "hotspot" && !nativeStartAttempted) {
1509
+ ownership.outcome = "rejected"
1510
+ this.clearStuckTimeout()
1511
+ await this.teardownHotspotTransport()
1512
+ if (this.attached) this.setErrorMsg(hotspotPreflightErrorMessage(err))
1513
+ return
1514
+ }
1515
+
1516
+ // A received ota_status/legacy progress event, or the exact legacy APK
1517
+ // build-number proof, proves that this logical start was accepted. Its
1518
+ // application-level ack may be lost while the update continues; never
1519
+ // create another transaction or fail a live, restarting, or completed
1520
+ // session because that stale promise timed out.
1521
+ if (this.hasFirstActivity || this.apkCompletedViaBuildIncrease) {
1522
+ ownership.outcome = "acknowledged"
1523
+ console.log("[OTA_PROGRESS] ota_start rejection arrived after acceptance proof — ignoring stale rejection")
1524
+ return
1525
+ }
1526
+
1527
+ ownership.outcome = "rejected"
1528
+ if (!this.attached) return
1529
+ this.clearStuckTimeout()
1530
+ if (!isGlassesConnected(useGlassesStore.getState().connection)) {
1531
+ console.log(
1532
+ "[OTA_PROGRESS] ota_start request ended while disconnected; deferring retry to reconnect arbitration",
1533
+ )
1534
+ return
1535
+ }
1536
+ if (this.retryCount < MAX_RETRIES - 1) {
1537
+ this.retryCount += 1
1538
+ const retryIntervalMs = this.isLegacySessionShapeNow() ? LEGACY_RETRY_INTERVAL_MS : RETRY_INTERVAL_MS
1539
+ console.log(
1540
+ `[OTA_PROGRESS] ota_start request ended without ack or activity; retrying after ${retryIntervalMs}ms (attempt ${
1541
+ this.retryCount + 1
1542
+ }/${MAX_RETRIES})`,
1543
+ )
1544
+ this.retryTimeout = setTimeout(() => {
1545
+ this.retryTimeout = null
1546
+ if (!isGlassesConnected(useGlassesStore.getState().connection)) {
1547
+ console.log("[OTA_PROGRESS] ota_start retry paused while disconnected; reconnect arbitration will resume")
1548
+ return
1549
+ }
1550
+ void this.sendOtaStartWithWatchdogs()
1551
+ }, retryIntervalMs)
1552
+ } else {
1553
+ console.log("[OTA_PROGRESS] sendOtaStart failed after max retries, failing session")
1554
+ this.setErrorMsg(OtaProgressMessages.sendOtaStartFailed)
1555
+ }
1556
+ }
1557
+ }
1558
+
1559
+ private async teardownHotspotTransport(): Promise<void> {
1560
+ if (this.selectedTransport !== "hotspot" && !this.hotspotManifestUrl) return
1561
+ await hotspotOtaTransport.teardown()
1562
+ this.hotspotManifestUrl = null
1563
+ this.hotspotPhase = "idle"
1564
+ this.hotspotArtifactPercent = null
1565
+ this.emitInternalChange()
1566
+ }
1567
+
1568
+ /**
1569
+ * After sending ota_query_status, wait QUERY_REPLY_TIMEOUT_MS for the glasses
1570
+ * to reply with a useful ota_status. A unified session may start again only
1571
+ * after an explicit "idle" reply, or after a locally rejected ota_start has
1572
+ * produced no activity and no authoritative reply across reconnect. Legacy
1573
+ * builds ignore ota_query_status, so their established timeout fallback
1574
+ * remains the compatibility path.
1575
+ *
1576
+ * Cleared as soon as a non-idle otaStatus or any otaProgress lands in the
1577
+ * store (see the reaction block above). An idle reply intentionally does NOT
1578
+ * cancel the fallback, so reconnects against a wiped/lost session still recover.
1579
+ */
1580
+ private armQueryReplyFallback(reason: "reconnect" | "initial-mount" | "retry"): void {
1581
+ this.clearQueryReplyTimeout()
1582
+ this.queryReplyTimeout = setTimeout(() => {
1583
+ this.queryReplyTimeout = null
1584
+ const state = useGlassesStore.getState()
1585
+ // If we already got a useful reply, we'd have been cleared. Defensive
1586
+ // re-check: idle replies must not block the retry. Legacy-shaped state is
1587
+ // exempt (WP 8C-b): old (< 37) builds ignore ota_query_status entirely, so
1588
+ // whatever legacy-shaped status/progress sits in the store is a pre-query
1589
+ // leftover, not a reply — a NEW legacy event would have cleared this timer
1590
+ // through the reaction pass. Only a unified reply suppresses the fallback.
1591
+ const legacySession = this.isLegacySessionShapeNow()
1592
+ if (!legacySession && hasRecoveringOtaReply(state.otaStatus, state.otaProgress)) return
1593
+ const explicitIdle = state.otaStatus?.status === "idle"
1594
+ const rejectedStart = this.otaStartOwnership?.outcome === "rejected"
1595
+ if (!legacySession && !explicitIdle && !rejectedStart) {
1596
+ console.log(
1597
+ `[OTA_PROGRESS] watchdog: ota_query_status got no authoritative idle reply in ${QUERY_REPLY_TIMEOUT_MS}ms (reason=${reason}); preserving the existing session`,
1598
+ )
1599
+ return
1600
+ }
1601
+ // Don't fire if we've left the active phase (e.g. user backed out, error overlay).
1602
+ if (isTerminalForWatchdog(this.computeDisplayStateNow())) return
1603
+ let fallbackCause = "legacy query timeout"
1604
+ if (explicitIdle) fallbackCause = "idle ota_status"
1605
+ else if (rejectedStart) fallbackCause = "rejected ota_start with no recovery reply"
1606
+ console.log(`[OTA_PROGRESS] watchdog: ${fallbackCause} (reason=${reason}), falling back to ota_start`)
1607
+ this.retryCount = 0
1608
+ if (this.otaStartOwnership?.outcome !== "pending" && !this.prepareFreshOtaStartAttempt()) return
1609
+ void this.sendOtaStartWithWatchdogs()
1610
+ }, QUERY_REPLY_TIMEOUT_MS)
1611
+ }
1612
+
1613
+ // --- timer cleanup helpers ---
1614
+
1615
+ private clearProgressTimeout(): void {
1616
+ if (this.progressTimeout) {
1617
+ clearTimeout(this.progressTimeout)
1618
+ this.progressTimeout = null
1619
+ }
1620
+ }
1621
+
1622
+ private clearRetryTimeout(): void {
1623
+ if (this.retryTimeout) {
1624
+ clearTimeout(this.retryTimeout)
1625
+ this.retryTimeout = null
1626
+ }
1627
+ }
1628
+
1629
+ private clearStuckTimeout(): void {
1630
+ if (this.stuckTimeout) {
1631
+ clearTimeout(this.stuckTimeout)
1632
+ this.stuckTimeout = null
1633
+ }
1634
+ }
1635
+
1636
+ private clearGlobalTimeout(): void {
1637
+ if (this.globalTimeout) {
1638
+ clearTimeout(this.globalTimeout)
1639
+ this.globalTimeout = null
1640
+ }
1641
+ this.globalTimeoutStarted = false
1642
+ }
1643
+
1644
+ private clearPingInterval(): void {
1645
+ if (this.pingInterval) {
1646
+ clearInterval(this.pingInterval)
1647
+ this.pingInterval = null
1648
+ }
1649
+ }
1650
+
1651
+ private clearQueryReplyTimeout(): void {
1652
+ if (this.queryReplyTimeout) {
1653
+ clearTimeout(this.queryReplyTimeout)
1654
+ this.queryReplyTimeout = null
1655
+ }
1656
+ }
1657
+
1658
+ private clearContinueLockoutTimer(): void {
1659
+ if (this.continueLockoutTimer) {
1660
+ clearTimeout(this.continueLockoutTimer)
1661
+ this.continueLockoutTimer = null
1662
+ }
1663
+ }
1664
+
1665
+ private clearLegacyApkSettleTimer(): void {
1666
+ if (this.legacyApkSettleTimer) {
1667
+ clearTimeout(this.legacyApkSettleTimer)
1668
+ this.legacyApkSettleTimer = null
1669
+ }
1670
+ }
1671
+
1672
+ private clearMtkSimulationTimers(): void {
1673
+ if (this.mtkStallDetectTimer) {
1674
+ clearTimeout(this.mtkStallDetectTimer)
1675
+ this.mtkStallDetectTimer = null
1676
+ }
1677
+ if (this.mtkSimTickTimer) {
1678
+ clearInterval(this.mtkSimTickTimer)
1679
+ this.mtkSimTickTimer = null
1680
+ }
1681
+ }
1682
+
1683
+ /**
1684
+ * NOTE (WP 8C): the legacy apk settle timer and the MTK stall simulation timers are
1685
+ * deliberately NOT per-step watchdogs — a per-step failure must not kill them (the
1686
+ * legacy screen kept the simulation ticking through a watchdog failure, and the
1687
+ * settle latch resolves completion, not liveness). They are cleared on detach,
1688
+ * retry(), global timeout, and their own supersession paths.
1689
+ */
1690
+ private clearPerStepTimers(): void {
1691
+ this.clearRetryTimeout()
1692
+ this.clearStuckTimeout()
1693
+ this.clearProgressTimeout()
1694
+ this.clearQueryReplyTimeout()
1695
+ }
1696
+
1697
+ private clearAllOtaTimers(): void {
1698
+ this.clearPerStepTimers()
1699
+ this.clearGlobalTimeout()
1700
+ this.clearPingInterval()
1701
+ this.clearLegacyApkSettleTimer()
1702
+ this.clearMtkSimulationTimers()
1703
+ }
1704
+ }
1705
+
1706
+ /** Singleton — one install session machine per app process (matches the single progress screen). */
1707
+ export const otaInstallCoordinator = new OtaInstallCoordinator()