@mentra/engine 0.1.0-dev.0 → 3.1.0-dev.4

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 (503) hide show
  1. package/README.md +55 -18
  2. package/build/bluetooth-sdk/debug.d.ts +2 -0
  3. package/build/bluetooth-sdk/debug.d.ts.map +1 -0
  4. package/build/bluetooth-sdk/debug.js +2 -0
  5. package/build/bluetooth-sdk/debug.js.map +1 -0
  6. package/build/bluetooth-sdk/index.d.ts +3 -0
  7. package/build/bluetooth-sdk/index.d.ts.map +1 -0
  8. package/build/bluetooth-sdk/index.js +3 -0
  9. package/build/bluetooth-sdk/index.js.map +1 -0
  10. package/build/bluetooth-sdk/photo-receiver.d.ts +3 -0
  11. package/build/bluetooth-sdk/photo-receiver.d.ts.map +1 -0
  12. package/build/bluetooth-sdk/photo-receiver.js +3 -0
  13. package/build/bluetooth-sdk/photo-receiver.js.map +1 -0
  14. package/build/bluetooth-sdk/react.d.ts +2 -0
  15. package/build/bluetooth-sdk/react.d.ts.map +1 -0
  16. package/build/bluetooth-sdk/react.js +2 -0
  17. package/build/bluetooth-sdk/react.js.map +1 -0
  18. package/build/bluetooth-sdk/types.d.ts +2 -0
  19. package/build/bluetooth-sdk/types.d.ts.map +1 -0
  20. package/build/bluetooth-sdk/types.js +2 -0
  21. package/build/bluetooth-sdk/types.js.map +1 -0
  22. package/build/engine.d.ts +24 -16
  23. package/build/engine.d.ts.map +1 -1
  24. package/build/engine.js +9 -4
  25. package/build/engine.js.map +1 -1
  26. package/build/facades/dev.d.ts +3 -2
  27. package/build/facades/dev.d.ts.map +1 -1
  28. package/build/facades/dev.js +18 -12
  29. package/build/facades/dev.js.map +1 -1
  30. package/build/facades/display.d.ts +1 -1
  31. package/build/facades/display.d.ts.map +1 -1
  32. package/build/facades/display.js +1 -1
  33. package/build/facades/display.js.map +1 -1
  34. package/build/facades/gallery.js +1 -1
  35. package/build/facades/gallery.js.map +1 -1
  36. package/build/facades/glasses.d.ts +4 -13
  37. package/build/facades/glasses.d.ts.map +1 -1
  38. package/build/facades/glasses.js +21 -6
  39. package/build/facades/glasses.js.map +1 -1
  40. package/build/facades/glassesSettings.d.ts.map +1 -1
  41. package/build/facades/glassesSettings.js +1 -0
  42. package/build/facades/glassesSettings.js.map +1 -1
  43. package/build/facades/miniapps.d.ts +4 -0
  44. package/build/facades/miniapps.d.ts.map +1 -1
  45. package/build/facades/miniapps.js +5 -0
  46. package/build/facades/miniapps.js.map +1 -1
  47. package/build/facades/ota.d.ts +14 -16
  48. package/build/facades/ota.d.ts.map +1 -1
  49. package/build/facades/ota.js +22 -7
  50. package/build/facades/ota.js.map +1 -1
  51. package/build/facades/pairing.d.ts +8 -5
  52. package/build/facades/pairing.d.ts.map +1 -1
  53. package/build/facades/pairing.js +12 -10
  54. package/build/facades/pairing.js.map +1 -1
  55. package/build/facades/permissions.d.ts +10 -0
  56. package/build/facades/permissions.d.ts.map +1 -1
  57. package/build/facades/permissions.js +10 -0
  58. package/build/facades/permissions.js.map +1 -1
  59. package/build/facades/phoneNotifications.d.ts +0 -4
  60. package/build/facades/phoneNotifications.d.ts.map +1 -1
  61. package/build/facades/phoneNotifications.js +4 -14
  62. package/build/facades/phoneNotifications.js.map +1 -1
  63. package/build/facades/speech.d.ts +7 -6
  64. package/build/facades/speech.d.ts.map +1 -1
  65. package/build/facades/speech.js +1 -1
  66. package/build/facades/speech.js.map +1 -1
  67. package/build/generated/releaseMetadata.d.ts +12 -0
  68. package/build/generated/releaseMetadata.d.ts.map +1 -0
  69. package/build/generated/releaseMetadata.js +10 -0
  70. package/build/generated/releaseMetadata.js.map +1 -0
  71. package/build/index.d.ts +10 -8
  72. package/build/index.d.ts.map +1 -1
  73. package/build/index.js +6 -5
  74. package/build/index.js.map +1 -1
  75. package/build/react/MentraLiveOtaFlow.d.ts +38 -0
  76. package/build/react/MentraLiveOtaFlow.d.ts.map +1 -0
  77. package/build/react/MentraLiveOtaFlow.js +528 -0
  78. package/build/react/MentraLiveOtaFlow.js.map +1 -0
  79. package/build/react/index.d.ts +2 -0
  80. package/build/react/index.d.ts.map +1 -0
  81. package/build/react/index.js +2 -0
  82. package/build/react/index.js.map +1 -0
  83. package/build/react/useEngineSnapshot.d.ts +2 -0
  84. package/build/react/useEngineSnapshot.d.ts.map +1 -0
  85. package/build/react/useEngineSnapshot.js +14 -0
  86. package/build/react/useEngineSnapshot.js.map +1 -0
  87. package/build/runtime/bootstrap.d.ts +24 -1
  88. package/build/runtime/bootstrap.d.ts.map +1 -1
  89. package/build/runtime/bootstrap.js +17 -0
  90. package/build/runtime/bootstrap.js.map +1 -1
  91. package/build/runtime/scanQrSeam.d.ts +7 -0
  92. package/build/runtime/scanQrSeam.d.ts.map +1 -0
  93. package/build/runtime/scanQrSeam.js +16 -0
  94. package/build/runtime/scanQrSeam.js.map +1 -0
  95. package/build/services/AppRegistry.d.ts +41 -57
  96. package/build/services/AppRegistry.d.ts.map +1 -1
  97. package/build/services/AppRegistry.js +208 -144
  98. package/build/services/AppRegistry.js.map +1 -1
  99. package/build/services/AudioCloudUplink.d.ts +12 -0
  100. package/build/services/AudioCloudUplink.d.ts.map +1 -1
  101. package/build/services/AudioCloudUplink.js +22 -1
  102. package/build/services/AudioCloudUplink.js.map +1 -1
  103. package/build/services/AudioPlaybackService.d.ts +41 -2
  104. package/build/services/AudioPlaybackService.d.ts.map +1 -1
  105. package/build/services/AudioPlaybackService.js +250 -33
  106. package/build/services/AudioPlaybackService.js.map +1 -1
  107. package/build/services/BlobStore.d.ts +2 -1
  108. package/build/services/BlobStore.d.ts.map +1 -1
  109. package/build/services/BlobStore.js +17 -9
  110. package/build/services/BlobStore.js.map +1 -1
  111. package/build/services/CloudAudioSubscriptionSync.d.ts.map +1 -1
  112. package/build/services/CloudAudioSubscriptionSync.js +13 -1
  113. package/build/services/CloudAudioSubscriptionSync.js.map +1 -1
  114. package/build/services/CloudClientService.d.ts +15 -0
  115. package/build/services/CloudClientService.d.ts.map +1 -1
  116. package/build/services/CloudClientService.js +93 -1
  117. package/build/services/CloudClientService.js.map +1 -1
  118. package/build/services/DeviceEventRouter.js +5 -5
  119. package/build/services/DeviceEventRouter.js.map +1 -1
  120. package/build/services/ForegroundLocationPermission.d.ts +13 -0
  121. package/build/services/ForegroundLocationPermission.d.ts.map +1 -0
  122. package/build/services/ForegroundLocationPermission.js +12 -0
  123. package/build/services/ForegroundLocationPermission.js.map +1 -0
  124. package/build/services/GlassesSettingsSync.d.ts.map +1 -1
  125. package/build/services/GlassesSettingsSync.js +9 -3
  126. package/build/services/GlassesSettingsSync.js.map +1 -1
  127. package/build/services/GlassesStatusProjection.d.ts +2 -1
  128. package/build/services/GlassesStatusProjection.d.ts.map +1 -1
  129. package/build/services/GlassesStatusProjection.js +15 -8
  130. package/build/services/GlassesStatusProjection.js.map +1 -1
  131. package/build/services/HotspotOtaTransport.d.ts +29 -0
  132. package/build/services/HotspotOtaTransport.d.ts.map +1 -0
  133. package/build/services/HotspotOtaTransport.js +146 -0
  134. package/build/services/HotspotOtaTransport.js.map +1 -0
  135. package/build/services/HotspotShutdown.d.ts +17 -0
  136. package/build/services/HotspotShutdown.d.ts.map +1 -0
  137. package/build/services/HotspotShutdown.js +29 -0
  138. package/build/services/HotspotShutdown.js.map +1 -0
  139. package/build/services/LocalDisplayManager.d.ts +15 -0
  140. package/build/services/LocalDisplayManager.d.ts.map +1 -1
  141. package/build/services/LocalDisplayManager.js +106 -20
  142. package/build/services/LocalDisplayManager.js.map +1 -1
  143. package/build/services/LocalMiniappRuntime.d.ts +74 -2
  144. package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
  145. package/build/services/LocalMiniappRuntime.js +785 -291
  146. package/build/services/LocalMiniappRuntime.js.map +1 -1
  147. package/build/services/LocalMiniappStorage.d.ts +32 -0
  148. package/build/services/LocalMiniappStorage.d.ts.map +1 -0
  149. package/build/services/LocalMiniappStorage.js +66 -0
  150. package/build/services/LocalMiniappStorage.js.map +1 -0
  151. package/build/services/LocalMiniappUserIdentity.d.ts +20 -0
  152. package/build/services/LocalMiniappUserIdentity.d.ts.map +1 -0
  153. package/build/services/LocalMiniappUserIdentity.js +43 -0
  154. package/build/services/LocalMiniappUserIdentity.js.map +1 -0
  155. package/build/services/LocalSttFallbackCoordinator.d.ts +9 -6
  156. package/build/services/LocalSttFallbackCoordinator.d.ts.map +1 -1
  157. package/build/services/LocalSttFallbackCoordinator.js +18 -12
  158. package/build/services/LocalSttFallbackCoordinator.js.map +1 -1
  159. package/build/services/MicStateCoordinator.d.ts +42 -1
  160. package/build/services/MicStateCoordinator.d.ts.map +1 -1
  161. package/build/services/MicStateCoordinator.js +141 -3
  162. package/build/services/MicStateCoordinator.js.map +1 -1
  163. package/build/services/MiniappLauncher.d.ts.map +1 -1
  164. package/build/services/MiniappLauncher.js +23 -3
  165. package/build/services/MiniappLauncher.js.map +1 -1
  166. package/build/services/MiniappLiveness.d.ts +12 -0
  167. package/build/services/MiniappLiveness.d.ts.map +1 -0
  168. package/build/services/MiniappLiveness.js +16 -0
  169. package/build/services/MiniappLiveness.js.map +1 -0
  170. package/build/services/NativeHttpResponse.d.ts +3 -0
  171. package/build/services/NativeHttpResponse.d.ts.map +1 -0
  172. package/build/services/NativeHttpResponse.js +6 -0
  173. package/build/services/NativeHttpResponse.js.map +1 -0
  174. package/build/services/OtaArtifactDownloader.d.ts +65 -0
  175. package/build/services/OtaArtifactDownloader.d.ts.map +1 -0
  176. package/build/services/OtaArtifactDownloader.js +226 -0
  177. package/build/services/OtaArtifactDownloader.js.map +1 -0
  178. package/build/services/OtaAutoChain.d.ts +34 -0
  179. package/build/services/OtaAutoChain.d.ts.map +1 -0
  180. package/build/services/OtaAutoChain.js +83 -0
  181. package/build/services/OtaAutoChain.js.map +1 -0
  182. package/build/services/OtaErrorMapping.d.ts +18 -0
  183. package/build/services/OtaErrorMapping.d.ts.map +1 -0
  184. package/build/services/OtaErrorMapping.js +63 -0
  185. package/build/services/OtaErrorMapping.js.map +1 -0
  186. package/build/services/OtaInstallCoordinator.d.ts +95 -15
  187. package/build/services/OtaInstallCoordinator.d.ts.map +1 -1
  188. package/build/services/OtaInstallCoordinator.js +577 -130
  189. package/build/services/OtaInstallCoordinator.js.map +1 -1
  190. package/build/services/OtaService.d.ts.map +1 -1
  191. package/build/services/OtaService.js +8 -1
  192. package/build/services/OtaService.js.map +1 -1
  193. package/build/services/OtaUpdateCheckService.d.ts +39 -4
  194. package/build/services/OtaUpdateCheckService.d.ts.map +1 -1
  195. package/build/services/OtaUpdateCheckService.js +138 -22
  196. package/build/services/OtaUpdateCheckService.js.map +1 -1
  197. package/build/services/PhoneCalendarHelpers.d.ts +32 -0
  198. package/build/services/PhoneCalendarHelpers.d.ts.map +1 -0
  199. package/build/services/PhoneCalendarHelpers.js +157 -0
  200. package/build/services/PhoneCalendarHelpers.js.map +1 -0
  201. package/build/services/PhoneCalendarService.d.ts +5 -0
  202. package/build/services/PhoneCalendarService.d.ts.map +1 -0
  203. package/build/services/PhoneCalendarService.js +45 -0
  204. package/build/services/PhoneCalendarService.js.map +1 -0
  205. package/build/services/PhoneCameraFovCoordinator.d.ts +30 -0
  206. package/build/services/PhoneCameraFovCoordinator.d.ts.map +1 -0
  207. package/build/services/PhoneCameraFovCoordinator.js +194 -0
  208. package/build/services/PhoneCameraFovCoordinator.js.map +1 -0
  209. package/build/services/PhoneNotificationsSync.d.ts.map +1 -1
  210. package/build/services/PhoneNotificationsSync.js +12 -10
  211. package/build/services/PhoneNotificationsSync.js.map +1 -1
  212. package/build/services/PhonePhotoCoordinator.d.ts +26 -2
  213. package/build/services/PhonePhotoCoordinator.d.ts.map +1 -1
  214. package/build/services/PhonePhotoCoordinator.js +79 -9
  215. package/build/services/PhonePhotoCoordinator.js.map +1 -1
  216. package/build/services/PhoneStreamCoordinator.d.ts +8 -1
  217. package/build/services/PhoneStreamCoordinator.d.ts.map +1 -1
  218. package/build/services/PhoneStreamCoordinator.js +125 -13
  219. package/build/services/PhoneStreamCoordinator.js.map +1 -1
  220. package/build/services/PhoneVideoCoordinator.d.ts +2 -0
  221. package/build/services/PhoneVideoCoordinator.d.ts.map +1 -1
  222. package/build/services/PhoneVideoCoordinator.js +10 -0
  223. package/build/services/PhoneVideoCoordinator.js.map +1 -1
  224. package/build/services/SceneRenderer.d.ts.map +1 -1
  225. package/build/services/SceneRenderer.js +15 -0
  226. package/build/services/SceneRenderer.js.map +1 -1
  227. package/build/services/SentenceTtsPipeline.d.ts +31 -0
  228. package/build/services/SentenceTtsPipeline.d.ts.map +1 -0
  229. package/build/services/SentenceTtsPipeline.js +67 -0
  230. package/build/services/SentenceTtsPipeline.js.map +1 -0
  231. package/build/services/SupportProfileSync.d.ts +22 -0
  232. package/build/services/SupportProfileSync.d.ts.map +1 -0
  233. package/build/services/SupportProfileSync.js +213 -0
  234. package/build/services/SupportProfileSync.js.map +1 -0
  235. package/build/services/TranscriptionRouting.d.ts +12 -0
  236. package/build/services/TranscriptionRouting.d.ts.map +1 -0
  237. package/build/services/TranscriptionRouting.js +24 -0
  238. package/build/services/TranscriptionRouting.js.map +1 -0
  239. package/build/services/TtsTextSanitizer.d.ts +5 -0
  240. package/build/services/TtsTextSanitizer.d.ts.map +1 -0
  241. package/build/services/TtsTextSanitizer.js +61 -0
  242. package/build/services/TtsTextSanitizer.js.map +1 -0
  243. package/build/services/WifiAdbRequest.d.ts +17 -0
  244. package/build/services/WifiAdbRequest.d.ts.map +1 -0
  245. package/build/services/WifiAdbRequest.js +35 -0
  246. package/build/services/WifiAdbRequest.js.map +1 -0
  247. package/build/services/asg/asgCameraApi.d.ts +43 -0
  248. package/build/services/asg/asgCameraApi.d.ts.map +1 -1
  249. package/build/services/asg/asgCameraApi.js +509 -26
  250. package/build/services/asg/asgCameraApi.js.map +1 -1
  251. package/build/services/asg/cameraRollExportCoordinator.d.ts +60 -0
  252. package/build/services/asg/cameraRollExportCoordinator.d.ts.map +1 -0
  253. package/build/services/asg/cameraRollExportCoordinator.js +618 -0
  254. package/build/services/asg/cameraRollExportCoordinator.js.map +1 -0
  255. package/build/services/asg/cameraRollExportLedger.d.ts +44 -0
  256. package/build/services/asg/cameraRollExportLedger.d.ts.map +1 -0
  257. package/build/services/asg/cameraRollExportLedger.js +78 -0
  258. package/build/services/asg/cameraRollExportLedger.js.map +1 -0
  259. package/build/services/asg/galleryNotices.d.ts +1 -1
  260. package/build/services/asg/galleryNotices.d.ts.map +1 -1
  261. package/build/services/asg/galleryNotices.js.map +1 -1
  262. package/build/services/asg/galleryPathMigration.d.ts +3 -0
  263. package/build/services/asg/galleryPathMigration.d.ts.map +1 -0
  264. package/build/services/asg/galleryPathMigration.js +26 -0
  265. package/build/services/asg/galleryPathMigration.js.map +1 -0
  266. package/build/services/asg/gallerySettingsService.d.ts +1 -0
  267. package/build/services/asg/gallerySettingsService.d.ts.map +1 -1
  268. package/build/services/asg/gallerySettingsService.js +2 -0
  269. package/build/services/asg/gallerySettingsService.js.map +1 -1
  270. package/build/services/asg/gallerySyncService.d.ts.map +1 -1
  271. package/build/services/asg/gallerySyncService.js +162 -56
  272. package/build/services/asg/gallerySyncService.js.map +1 -1
  273. package/build/services/asg/galleryTransferLedger.d.ts +58 -0
  274. package/build/services/asg/galleryTransferLedger.d.ts.map +1 -0
  275. package/build/services/asg/galleryTransferLedger.js +225 -0
  276. package/build/services/asg/galleryTransferLedger.js.map +1 -0
  277. package/build/services/asg/localNetworkTransport.d.ts +15 -0
  278. package/build/services/asg/localNetworkTransport.d.ts.map +1 -0
  279. package/build/services/asg/localNetworkTransport.js +144 -0
  280. package/build/services/asg/localNetworkTransport.js.map +1 -0
  281. package/build/services/asg/localStorageService.d.ts +18 -0
  282. package/build/services/asg/localStorageService.d.ts.map +1 -1
  283. package/build/services/asg/localStorageService.js +168 -19
  284. package/build/services/asg/localStorageService.js.map +1 -1
  285. package/build/services/asg/mediaProcessingQueue.d.ts +10 -0
  286. package/build/services/asg/mediaProcessingQueue.d.ts.map +1 -1
  287. package/build/services/asg/mediaProcessingQueue.js +193 -62
  288. package/build/services/asg/mediaProcessingQueue.js.map +1 -1
  289. package/build/services/audioPlaybackAssets.d.ts +8 -0
  290. package/build/services/audioPlaybackAssets.d.ts.map +1 -0
  291. package/build/services/audioPlaybackAssets.js +7 -0
  292. package/build/services/audioPlaybackAssets.js.map +1 -0
  293. package/build/services/gallerySyncClock.js +1 -1
  294. package/build/services/gallerySyncClock.js.map +1 -1
  295. package/build/services/glassesClockSync.d.ts.map +1 -1
  296. package/build/services/glassesClockSync.js +6 -2
  297. package/build/services/glassesClockSync.js.map +1 -1
  298. package/build/services/manifestActions.d.ts +7 -0
  299. package/build/services/manifestActions.d.ts.map +1 -0
  300. package/build/services/manifestActions.js +25 -0
  301. package/build/services/manifestActions.js.map +1 -0
  302. package/build/services/otaDisplayState.d.ts +23 -12
  303. package/build/services/otaDisplayState.d.ts.map +1 -1
  304. package/build/services/otaDisplayState.js +36 -14
  305. package/build/services/otaDisplayState.js.map +1 -1
  306. package/build/services/otaInstallPolicy.d.ts +16 -4
  307. package/build/services/otaInstallPolicy.d.ts.map +1 -1
  308. package/build/services/otaInstallPolicy.js +24 -3
  309. package/build/services/otaInstallPolicy.js.map +1 -1
  310. package/build/services/otaLegacyMapping.d.ts +1 -1
  311. package/build/services/otaLegacyMapping.d.ts.map +1 -1
  312. package/build/services/otaLegacyMapping.js.map +1 -1
  313. package/build/services/otaManifestPolicy.d.ts +14 -0
  314. package/build/services/otaManifestPolicy.d.ts.map +1 -0
  315. package/build/services/otaManifestPolicy.js +19 -0
  316. package/build/services/otaManifestPolicy.js.map +1 -0
  317. package/build/services/otaManifestUrl.d.ts +3 -0
  318. package/build/services/otaManifestUrl.d.ts.map +1 -0
  319. package/build/services/otaManifestUrl.js +38 -0
  320. package/build/services/otaManifestUrl.js.map +1 -0
  321. package/build/services/slimStreamStatus.d.ts +11 -0
  322. package/build/services/slimStreamStatus.d.ts.map +1 -1
  323. package/build/services/slimStreamStatus.js +14 -4
  324. package/build/services/slimStreamStatus.js.map +1 -1
  325. package/build/stores/apps.d.ts.map +1 -1
  326. package/build/stores/apps.js +11 -0
  327. package/build/stores/apps.js.map +1 -1
  328. package/build/stores/bluetoothSettingKeys.d.ts.map +1 -1
  329. package/build/stores/bluetoothSettingKeys.js +2 -0
  330. package/build/stores/bluetoothSettingKeys.js.map +1 -1
  331. package/build/stores/gallerySync.d.ts +2 -0
  332. package/build/stores/gallerySync.d.ts.map +1 -1
  333. package/build/stores/gallerySync.js +2 -0
  334. package/build/stores/gallerySync.js.map +1 -1
  335. package/build/stores/glasses.d.ts.map +1 -1
  336. package/build/stores/glasses.js +3 -0
  337. package/build/stores/glasses.js.map +1 -1
  338. package/build/stores/settings.d.ts +17 -0
  339. package/build/stores/settings.d.ts.map +1 -1
  340. package/build/stores/settings.js +157 -17
  341. package/build/stores/settings.js.map +1 -1
  342. package/build/types/applet.d.ts +2 -0
  343. package/build/types/applet.d.ts.map +1 -1
  344. package/build/types/applet.js.map +1 -1
  345. package/build/types/asg.d.ts +7 -0
  346. package/build/types/asg.d.ts.map +1 -1
  347. package/build/types/asg.js.map +1 -1
  348. package/build/types/enums.d.ts +2 -25
  349. package/build/types/enums.d.ts.map +1 -1
  350. package/build/types/enums.js +2 -27
  351. package/build/types/enums.js.map +1 -1
  352. package/build/types/hardware.d.ts +4 -13
  353. package/build/types/hardware.d.ts.map +1 -1
  354. package/build/types/hardware.js +15 -3
  355. package/build/types/hardware.js.map +1 -1
  356. package/build/types/index.d.ts +2 -6
  357. package/build/types/index.d.ts.map +1 -1
  358. package/build/types/index.js +2 -6
  359. package/build/types/index.js.map +1 -1
  360. package/build/utils/configuredDevHost.d.ts +6 -0
  361. package/build/utils/configuredDevHost.d.ts.map +1 -0
  362. package/build/utils/configuredDevHost.js +19 -0
  363. package/build/utils/configuredDevHost.js.map +1 -0
  364. package/build/utils/devMiniappLaunch.d.ts +19 -8
  365. package/build/utils/devMiniappLaunch.d.ts.map +1 -1
  366. package/build/utils/devMiniappLaunch.js +144 -21
  367. package/build/utils/devMiniappLaunch.js.map +1 -1
  368. package/build/utils/diagnosticContext.d.ts.map +1 -1
  369. package/build/utils/diagnosticContext.js +15 -17
  370. package/build/utils/diagnosticContext.js.map +1 -1
  371. package/build/utils/hardware/hardware.d.ts.map +1 -1
  372. package/build/utils/hardware/hardware.js +7 -3
  373. package/build/utils/hardware/hardware.js.map +1 -1
  374. package/build/utils/miniappDiagnostics.d.ts +64 -0
  375. package/build/utils/miniappDiagnostics.d.ts.map +1 -0
  376. package/build/utils/miniappDiagnostics.js +173 -0
  377. package/build/utils/miniappDiagnostics.js.map +1 -0
  378. package/build/utils/permissions/MediaLibraryPermissions.d.ts +11 -0
  379. package/build/utils/permissions/MediaLibraryPermissions.d.ts.map +1 -1
  380. package/build/utils/permissions/MediaLibraryPermissions.js +27 -4
  381. package/build/utils/permissions/MediaLibraryPermissions.js.map +1 -1
  382. package/build/utils/timers/timers.d.ts.map +1 -1
  383. package/build/utils/timers/timers.js +55 -18
  384. package/build/utils/timers/timers.js.map +1 -1
  385. package/package.json +59 -31
  386. package/src/assets/silence.wav +0 -0
  387. package/src/bluetooth-sdk/debug.ts +1 -0
  388. package/src/bluetooth-sdk/index.ts +2 -0
  389. package/src/bluetooth-sdk/photo-receiver.ts +2 -0
  390. package/src/bluetooth-sdk/react.ts +1 -0
  391. package/src/bluetooth-sdk/types.ts +1 -0
  392. package/src/engine.ts +9 -4
  393. package/src/facades/dev.ts +17 -12
  394. package/src/facades/display.ts +1 -1
  395. package/src/facades/gallery.ts +1 -1
  396. package/src/facades/glasses.ts +20 -7
  397. package/src/facades/glassesSettings.ts +5 -0
  398. package/src/facades/miniapps.ts +7 -0
  399. package/src/facades/ota.ts +23 -8
  400. package/src/facades/pairing.ts +15 -11
  401. package/src/facades/permissions.ts +14 -0
  402. package/src/facades/phoneNotifications.ts +4 -13
  403. package/src/facades/speech.ts +21 -7
  404. package/src/generated/releaseMetadata.ts +20 -0
  405. package/src/index.ts +39 -13
  406. package/src/react/MentraLiveOtaFlow.tsx +856 -0
  407. package/src/react/index.ts +7 -0
  408. package/src/react/useEngineSnapshot.ts +16 -0
  409. package/src/runtime/bootstrap.ts +33 -1
  410. package/src/runtime/scanQrSeam.ts +20 -0
  411. package/src/services/AppRegistry.ts +240 -149
  412. package/src/services/AudioCloudUplink.ts +23 -1
  413. package/src/services/AudioPlaybackService.ts +287 -38
  414. package/src/services/BlobStore.ts +22 -9
  415. package/src/services/CloudAudioSubscriptionSync.ts +13 -1
  416. package/src/services/CloudClientService.ts +96 -1
  417. package/src/services/DeviceEventRouter.ts +5 -5
  418. package/src/services/ForegroundLocationPermission.ts +24 -0
  419. package/src/services/GlassesSettingsSync.ts +9 -3
  420. package/src/services/GlassesStatusProjection.ts +16 -8
  421. package/src/services/HotspotOtaTransport.ts +184 -0
  422. package/src/services/HotspotShutdown.ts +41 -0
  423. package/src/services/LocalDisplayManager.ts +112 -21
  424. package/src/services/LocalMiniappRuntime.ts +967 -346
  425. package/src/services/LocalMiniappStorage.ts +82 -0
  426. package/src/services/LocalMiniappUserIdentity.ts +46 -0
  427. package/src/services/LocalSttFallbackCoordinator.ts +19 -12
  428. package/src/services/MicStateCoordinator.ts +169 -4
  429. package/src/services/MiniappLauncher.ts +30 -3
  430. package/src/services/MiniappLiveness.ts +24 -0
  431. package/src/services/NativeHttpResponse.ts +6 -0
  432. package/src/services/OtaArtifactDownloader.ts +312 -0
  433. package/src/services/OtaAutoChain.ts +106 -0
  434. package/src/services/OtaErrorMapping.ts +80 -0
  435. package/src/services/OtaInstallCoordinator.ts +649 -144
  436. package/src/services/OtaService.ts +11 -5
  437. package/src/services/OtaUpdateCheckService.ts +181 -21
  438. package/src/services/PhoneCalendarHelpers.ts +212 -0
  439. package/src/services/PhoneCalendarService.ts +65 -0
  440. package/src/services/PhoneCameraFovCoordinator.ts +210 -0
  441. package/src/services/PhoneNotificationsSync.ts +12 -10
  442. package/src/services/PhonePhotoCoordinator.ts +116 -18
  443. package/src/services/PhoneStreamCoordinator.ts +139 -25
  444. package/src/services/PhoneVideoCoordinator.ts +11 -0
  445. package/src/services/SceneRenderer.ts +16 -0
  446. package/src/services/SentenceTtsPipeline.ts +106 -0
  447. package/src/services/SupportProfileSync.ts +218 -0
  448. package/src/services/TranscriptionRouting.ts +37 -0
  449. package/src/services/TtsTextSanitizer.ts +66 -0
  450. package/src/services/WifiAdbRequest.ts +53 -0
  451. package/src/services/asg/asgCameraApi.ts +615 -33
  452. package/src/services/asg/cameraRollExportCoordinator.ts +627 -0
  453. package/src/services/asg/cameraRollExportLedger.ts +122 -0
  454. package/src/services/asg/galleryNotices.ts +1 -0
  455. package/src/services/asg/galleryPathMigration.ts +22 -0
  456. package/src/services/asg/gallerySettingsService.ts +3 -0
  457. package/src/services/asg/gallerySyncService.ts +184 -73
  458. package/src/services/asg/galleryTransferLedger.ts +312 -0
  459. package/src/services/asg/localNetworkTransport.ts +169 -0
  460. package/src/services/asg/localStorageService.ts +179 -24
  461. package/src/services/asg/mediaProcessingQueue.ts +209 -62
  462. package/src/services/audioPlaybackAssets.ts +8 -0
  463. package/src/services/gallerySyncClock.ts +1 -1
  464. package/src/services/glassesClockSync.ts +6 -2
  465. package/src/services/manifestActions.ts +30 -0
  466. package/src/services/otaDisplayState.ts +58 -17
  467. package/src/services/otaInstallPolicy.ts +30 -4
  468. package/src/services/otaLegacyMapping.ts +1 -1
  469. package/src/services/otaManifestPolicy.ts +42 -0
  470. package/src/services/otaManifestUrl.ts +44 -0
  471. package/src/services/slimStreamStatus.ts +16 -5
  472. package/src/stores/apps.ts +12 -0
  473. package/src/stores/bluetoothSettingKeys.ts +2 -0
  474. package/src/stores/gallerySync.ts +4 -0
  475. package/src/stores/glasses.ts +3 -0
  476. package/src/stores/settings.ts +179 -18
  477. package/src/types/applet.ts +2 -0
  478. package/src/types/asg.ts +7 -0
  479. package/src/types/enums.ts +3 -28
  480. package/src/types/hardware.ts +20 -13
  481. package/src/types/index.ts +2 -6
  482. package/src/utils/configuredDevHost.ts +17 -0
  483. package/src/utils/devMiniappLaunch.ts +151 -23
  484. package/src/utils/diagnosticContext.ts +17 -17
  485. package/src/utils/hardware/hardware.ts +7 -9
  486. package/src/utils/miniappDiagnostics.ts +226 -0
  487. package/src/utils/permissions/MediaLibraryPermissions.ts +35 -4
  488. package/src/utils/timers/timers.ts +57 -21
  489. package/build/devtools.d.ts +0 -11
  490. package/build/devtools.d.ts.map +0 -1
  491. package/build/devtools.js +0 -13
  492. package/build/devtools.js.map +0 -1
  493. package/build/internal.d.ts +0 -66
  494. package/build/internal.d.ts.map +0 -1
  495. package/build/internal.js +0 -89
  496. package/build/internal.js.map +0 -1
  497. package/build/services/asgOtaVersionUrl.d.ts +0 -2
  498. package/build/services/asgOtaVersionUrl.d.ts.map +0 -1
  499. package/build/services/asgOtaVersionUrl.js +0 -36
  500. package/build/services/asgOtaVersionUrl.js.map +0 -1
  501. package/src/devtools.ts +0 -13
  502. package/src/internal.ts +0 -158
  503. package/src/services/asgOtaVersionUrl.ts +0 -41
@@ -12,7 +12,8 @@ import { INVALID_DOWNLOADED_MEDIA, validateDownloadedMediaFile } from "./gallery
12
12
  import { reportInvalidGalleryMedia } from "./GalleryMediaIntegrityReportService";
13
13
  import { useGallerySyncStore } from "../../stores/gallerySync";
14
14
  import { BgTimer } from "../../utils/timers";
15
- import { MediaLibraryPermissions } from "../../utils/permissions/MediaLibraryPermissions";
15
+ import { galleryTransferLedger } from "./galleryTransferLedger";
16
+ import { cameraRollExportCoordinator } from "./cameraRollExportCoordinator";
16
17
  const TAG = "[MediaProcessingQueue]";
17
18
  class MediaProcessingQueue {
18
19
  queue = [];
@@ -50,7 +51,7 @@ class MediaProcessingQueue {
50
51
  return this.queue.length > 0 || this.isRunning;
51
52
  }
52
53
  /** Returns a promise that resolves when the queue is fully drained, or rejects on timeout. */
53
- waitUntilDrained(timeoutMs = 600000) {
54
+ waitUntilDrained(timeoutMs = 4 * 60 * 60 * 1000) {
54
55
  if (!this.hasPending)
55
56
  return Promise.resolve();
56
57
  return new Promise((resolve, reject) => {
@@ -71,6 +72,69 @@ class MediaProcessingQueue {
71
72
  check();
72
73
  });
73
74
  }
75
+ /** Retry durable export/ack work after an app restart without downloading media again. */
76
+ async retryPending() {
77
+ let retried = 0;
78
+ let failed = 0;
79
+ for (let entry of galleryTransferLedger.pendingRecovery()) {
80
+ try {
81
+ if (entry.state === "RESTORE_PENDING") {
82
+ await asgCameraApi.restoreCapture(entry.captureId);
83
+ galleryTransferLedger.transition(entry.captureId, "DISCOVERED", { lastError: undefined });
84
+ retried++;
85
+ continue;
86
+ }
87
+ if (!entry.finalPath || !(await RNFS.exists(entry.finalPath))) {
88
+ throw new Error(`Recovery file is missing for ${entry.captureId}`);
89
+ }
90
+ // Rebuild a missing app index before any source acknowledgement, even when camera-roll
91
+ // saving is disabled or an export receipt was already committed before restart.
92
+ const downloadedFile = await this.recoverDownloadedFile(entry);
93
+ if (entry.state === "EXPORT_PENDING" || entry.state === "INDEXED") {
94
+ const shouldAutoSave = await cameraRollExportCoordinator.isAutoSaveEnabled();
95
+ entry = galleryTransferLedger.update(entry.captureId, { shouldAutoSave, lastError: undefined });
96
+ if (shouldAutoSave) {
97
+ if (entry.state !== "EXPORT_PENDING") {
98
+ entry = galleryTransferLedger.transition(entry.captureId, "EXPORT_PENDING");
99
+ }
100
+ const receipt = await cameraRollExportCoordinator.exportForSource(downloadedFile, entry.captureId);
101
+ entry = receipt
102
+ ? galleryTransferLedger.transition(entry.captureId, "EXPORTED", { assetReceipt: receipt })
103
+ : galleryTransferLedger.transition(entry.captureId, "INDEXED", {
104
+ shouldAutoSave: false,
105
+ lastError: undefined,
106
+ });
107
+ }
108
+ else if (entry.state === "EXPORT_PENDING") {
109
+ entry = galleryTransferLedger.transition(entry.captureId, "INDEXED", {
110
+ shouldAutoSave: false,
111
+ lastError: undefined,
112
+ });
113
+ }
114
+ }
115
+ entry = galleryTransferLedger.transition(entry.captureId, "ACK_PENDING");
116
+ await this.acknowledgeEntry(entry);
117
+ galleryTransferLedger.transition(entry.captureId, "TRASHED", { lastError: undefined });
118
+ retried++;
119
+ }
120
+ catch (error) {
121
+ galleryTransferLedger.recordFailure(entry.captureId, error);
122
+ failed++;
123
+ console.warn(`${TAG} Recovery remains pending for ${entry.captureId}:`, error);
124
+ }
125
+ }
126
+ return { retried, failed };
127
+ }
128
+ async acknowledgeEntry(entry) {
129
+ if (entry.protocolVersion >= 3) {
130
+ await asgCameraApi.acknowledgeCapture(entry.captureId, entry.ackId);
131
+ return;
132
+ }
133
+ const result = await asgCameraApi.deleteFilesFromServer(entry.deleteFromGlasses.length > 0 ? entry.deleteFromGlasses : [entry.captureId]);
134
+ if (result.failed.length > 0 || result.deleted.length === 0) {
135
+ throw new Error(`Glasses did not acknowledge ${entry.captureId}`);
136
+ }
137
+ }
74
138
  /** Process items one at a time. */
75
139
  async processLoop() {
76
140
  if (this.isRunning)
@@ -86,6 +150,10 @@ class MediaProcessingQueue {
86
150
  catch (error) {
87
151
  console.error(`${TAG} Error processing ${item.id}:`, error);
88
152
  processFailed = true;
153
+ const store = useGallerySyncStore.getState();
154
+ if (!store.failedFiles.includes(item.id)) {
155
+ store.onFileFailed(item.id, error instanceof Error ? error.message : String(error));
156
+ }
89
157
  }
90
158
  // Exit if generation changed (reset was called during processing)
91
159
  if (this.generation !== myGeneration)
@@ -96,9 +164,8 @@ class MediaProcessingQueue {
96
164
  store.onFileProcessed(item.id);
97
165
  }
98
166
  else {
99
- // Failure: processItem already called onFileFailed. Calling onFileProcessed
100
- // here would double-count the item (counted as both failed AND processed).
101
- // Still clear the processingFiles entry so the UI doesn't show it as in-progress.
167
+ // Failure is counted exactly once above. Still clear the processingFiles entry so the UI
168
+ // does not show it as in-progress.
102
169
  const current = store.processingFiles;
103
170
  if (current.has(item.id)) {
104
171
  const newSet = new Set(current);
@@ -116,6 +183,20 @@ class MediaProcessingQueue {
116
183
  async processItem(item) {
117
184
  const startTime = Date.now();
118
185
  let filePathToSave = item.primaryPath;
186
+ let ledgerEntry = galleryTransferLedger.get(item.id);
187
+ if (!ledgerEntry) {
188
+ ledgerEntry = galleryTransferLedger.ensureCapture({
189
+ capture_id: item.id,
190
+ type: item.type,
191
+ timestamp: item.timestamp || Date.now(),
192
+ total_size: item.totalSize,
193
+ files: (item.deleteFromGlasses?.length ? item.deleteFromGlasses : [item.id]).map((name, index) => ({
194
+ name,
195
+ size: index === 0 ? item.totalSize : 0,
196
+ role: index === 0 ? "primary" : "sidecar",
197
+ })),
198
+ }, item.protocolVersion || 2);
199
+ }
119
200
  // 1. HDR merge (photos with brackets only)
120
201
  if (item.shouldProcess && item.type === "photo" && item.bracketPaths && item.bracketPaths.length >= 3) {
121
202
  try {
@@ -188,8 +269,8 @@ class MediaProcessingQueue {
188
269
  });
189
270
  }
190
271
  catch (validationError) {
191
- const reason = validationError?.message?.includes(INVALID_DOWNLOADED_MEDIA) ?
192
- validationError.message
272
+ const reason = validationError?.message?.includes(INVALID_DOWNLOADED_MEDIA)
273
+ ? validationError.message
193
274
  : `${INVALID_DOWNLOADED_MEDIA}: ${item.id}`;
194
275
  console.error(`${TAG} Validation failed for ${item.id}: ${reason}`);
195
276
  reportInvalidGalleryMedia({
@@ -204,54 +285,69 @@ class MediaProcessingQueue {
204
285
  });
205
286
  const store = useGallerySyncStore.getState();
206
287
  store.onFileFailed(item.id, reason);
207
- await RNFS.unlink(filePathToSave).catch(() => { });
208
- for (const intermediate of [
209
- item.primaryPath + ".hdr.jpg",
210
- item.primaryPath + ".processed.jpg",
211
- item.primaryPath + ".stabilized.mp4",
212
- ]) {
213
- if (intermediate !== filePathToSave) {
214
- await RNFS.unlink(intermediate).catch(() => { });
215
- }
216
- }
288
+ galleryTransferLedger.recordFailure(item.id, validationError);
217
289
  throw new Error(reason);
218
290
  }
219
- // 6. Save to camera roll
220
- if (item.shouldAutoSave) {
221
- const success = await MediaLibraryPermissions.saveToLibrary(filePathToSave, item.timestamp);
222
- if (success) {
223
- console.log(`${TAG} ✅ Saved to camera roll: ${item.id}`);
224
- }
225
- else {
226
- console.warn(`${TAG} ❌ Failed to save to camera roll: ${item.id}`);
227
- // D1: Surface camera roll save failures to the UI
228
- const store = useGallerySyncStore.getState();
229
- store.onFileFailed(item.id, "camera roll save failed");
230
- }
231
- }
232
- // 7. Save metadata
291
+ // 6. Commit the app-local index before any export or source acknowledgement. Use the final
292
+ // on-disk output metadata for camera-roll identity: processing can change byte size, while
293
+ // recovery reconstructs this row from the same file and the transfer ledger timestamp.
233
294
  const isVideo = item.type === "video";
295
+ const finalFileStat = await RNFS.stat(filePathToSave);
296
+ const finalFileSize = Number(finalFileStat.size);
234
297
  const downloadedFile = localStorageService.convertToDownloadedFile({
235
298
  name: item.id,
236
299
  url: "",
237
300
  download: "",
238
- size: item.totalSize,
239
- modified: item.timestamp || Date.now(),
301
+ size: finalFileSize,
302
+ modified: ledgerEntry.timestamp,
240
303
  is_video: isVideo,
241
304
  thumbnail_data: item.thumbnailData,
242
305
  duration: item.duration,
243
306
  filePath: filePathToSave,
244
307
  glassesModel: item.glassesModel,
245
308
  }, filePathToSave, localThumbnailPath, item.glassesModel);
309
+ downloadedFile.capture_id = item.id;
310
+ const shouldAutoSave = await cameraRollExportCoordinator.isAutoSaveEnabled();
246
311
  try {
247
312
  await localStorageService.saveDownloadedFile(downloadedFile);
313
+ ledgerEntry = galleryTransferLedger.transition(item.id, "INDEXED", {
314
+ finalPath: filePathToSave,
315
+ thumbnailPath: localThumbnailPath,
316
+ shouldAutoSave,
317
+ });
318
+ await cameraRollExportCoordinator.recordIndexed(downloadedFile, shouldAutoSave, shouldAutoSave ? "SOURCE_BARRIER" : "HISTORICAL_BACKFILL", item.id);
248
319
  }
249
320
  catch (metadataError) {
250
- // D2: Clean up orphaned file if metadata save fails, then re-throw
251
- console.error(`${TAG} Metadata save failed for ${item.id}, cleaning up file:`, metadataError);
252
- await RNFS.unlink(filePathToSave).catch(() => { });
321
+ console.error(`${TAG} Metadata save failed for ${item.id}; retaining local and glasses copies:`, metadataError);
322
+ galleryTransferLedger.recordFailure(item.id, metadataError);
253
323
  throw metadataError;
254
324
  }
325
+ // 7. While enabled, export is a durable, receipted step whose failures block ack. Turning
326
+ // auto-save off safely falls back to the already-verified app-local copy and later backfill.
327
+ if (shouldAutoSave) {
328
+ galleryTransferLedger.transition(item.id, "EXPORT_PENDING");
329
+ let receipt;
330
+ try {
331
+ receipt = await cameraRollExportCoordinator.exportForSource(downloadedFile, item.id);
332
+ }
333
+ catch (error) {
334
+ galleryTransferLedger.recordFailure(item.id, error);
335
+ throw error;
336
+ }
337
+ if (receipt) {
338
+ ledgerEntry = galleryTransferLedger.transition(item.id, "EXPORTED", {
339
+ assetReceipt: receipt,
340
+ });
341
+ console.log(`${TAG} ✅ Saved to camera roll with receipt: ${item.id}`);
342
+ }
343
+ else {
344
+ ledgerEntry = galleryTransferLedger.transition(item.id, "INDEXED", {
345
+ shouldAutoSave: false,
346
+ lastError: undefined,
347
+ });
348
+ console.log(`${TAG} Automatic camera-roll saving was disabled; keeping app-local copy: ${item.id}`);
349
+ }
350
+ }
255
351
  // S4: Clean up intermediate processing files
256
352
  const intermediates = [
257
353
  item.primaryPath + ".hdr.jpg",
@@ -275,8 +371,8 @@ class MediaProcessingQueue {
275
371
  name: item.id,
276
372
  url: localFileUrl,
277
373
  download: localFileUrl,
278
- size: item.totalSize,
279
- modified: item.timestamp || Date.now(),
374
+ size: finalFileSize,
375
+ modified: ledgerEntry.timestamp,
280
376
  is_video: isVideo,
281
377
  filePath: filePathToSave,
282
378
  mime_type: isVideo ? "video/mp4" : "image/jpeg",
@@ -284,36 +380,71 @@ class MediaProcessingQueue {
284
380
  thumbnailPath: localThumbUrl,
285
381
  duration: item.duration,
286
382
  });
287
- // 9. Delete from glasses now that processing is complete but only if the
288
- // local file actually exists and has data. If the download was truncated or
289
- // processing failed silently, we must not destroy the only good copy.
290
- if (item.deleteFromGlasses && item.deleteFromGlasses.length > 0) {
383
+ // 9. Acknowledge only after VERIFIED -> INDEXED -> (EXPORTED). The server turns this into
384
+ // recoverable trash; failures remain ACK_PENDING and are retried without re-downloading.
385
+ const localFileExists = await RNFS.exists(filePathToSave);
386
+ const localStat = localFileExists ? await RNFS.stat(filePathToSave) : null;
387
+ if (!localStat || localStat.size <= 0) {
388
+ const error = new Error(`Cannot acknowledge ${item.id}: committed local file is missing or empty`);
389
+ galleryTransferLedger.recordFailure(item.id, error);
390
+ throw error;
391
+ }
392
+ ledgerEntry = galleryTransferLedger.transition(item.id, "ACK_PENDING");
393
+ try {
394
+ await this.acknowledgeEntry(ledgerEntry);
395
+ galleryTransferLedger.transition(item.id, "TRASHED", { lastError: undefined });
396
+ }
397
+ catch (ackError) {
398
+ galleryTransferLedger.recordFailure(item.id, ackError);
399
+ throw ackError;
400
+ }
401
+ const elapsed = Date.now() - startTime;
402
+ console.log(`${TAG} ✅ Finished ${item.id} in ${elapsed}ms`);
403
+ }
404
+ /** Rebuild a missing app-local index row from the durable transfer ledger. */
405
+ async recoverDownloadedFile(entry) {
406
+ if (!entry.finalPath)
407
+ throw new Error(`Recovery file is missing for ${entry.captureId}`);
408
+ // The transfer ledger is the durable source of truth for a committed generation. A local
409
+ // index row can survive an interrupted replacement and still point at an older/missing path,
410
+ // or contain the pre-processing size. Only reuse it when its path and identity still match the
411
+ // ledger's committed file; otherwise rebuild the row before export or source acknowledgement.
412
+ const finalPath = entry.finalPath.replace(/^file:\/\//, "");
413
+ const indexed = await localStorageService.getDownloadedFile(entry.captureId);
414
+ if (indexed) {
291
415
  try {
292
- const localFileExists = await RNFS.exists(filePathToSave);
293
- if (!localFileExists) {
294
- console.error(`${TAG} Skipping glasses deletion for ${item.id}: local file missing at ${filePathToSave}`);
295
- }
296
- else {
297
- const localStat = await RNFS.stat(filePathToSave);
298
- if (localStat.size === 0) {
299
- console.error(`${TAG} Skipping glasses deletion for ${item.id}: local file is 0 bytes`);
300
- }
301
- else if (item.totalSize > 0 && localStat.size < item.totalSize * 0.5) {
302
- // If local file is less than 50% of expected size, something went wrong
303
- console.error(`${TAG} Skipping glasses deletion for ${item.id}: local file ${localStat.size} bytes is much smaller than expected ${item.totalSize} bytes`);
304
- }
305
- else {
306
- await asgCameraApi.deleteFilesFromServer(item.deleteFromGlasses);
307
- console.log(`${TAG} 🗑️ Deleted ${item.deleteFromGlasses.join(", ")} from glasses`);
416
+ const indexedPath = indexed.filePath?.replace(/^file:\/\//, "");
417
+ if (indexedPath === finalPath && (await RNFS.exists(indexedPath))) {
418
+ const indexedStat = await RNFS.stat(indexedPath);
419
+ const indexedSize = Number(indexedStat.size);
420
+ if (indexedSize > 0 && indexed.size === indexedSize && indexed.modified === entry.timestamp) {
421
+ return indexed;
308
422
  }
309
423
  }
310
424
  }
311
- catch (deleteError) {
312
- console.warn(`${TAG} Delete from glasses failed for ${item.id} (non-fatal):`, deleteError);
425
+ catch (error) {
426
+ console.warn(`${TAG} Ignoring stale local index for ${entry.captureId}:`, error);
313
427
  }
314
428
  }
315
- const elapsed = Date.now() - startTime;
316
- console.log(`${TAG} Finished ${item.id} in ${elapsed}ms`);
429
+ const stat = await RNFS.stat(finalPath);
430
+ const finalSize = Number(stat.size);
431
+ if (!Number.isFinite(finalSize) || finalSize <= 0) {
432
+ throw new Error(`Recovery file is missing or empty for ${entry.captureId}`);
433
+ }
434
+ const recovered = localStorageService.convertToDownloadedFile({
435
+ name: entry.captureId,
436
+ url: "",
437
+ download: "",
438
+ size: finalSize,
439
+ modified: entry.timestamp,
440
+ mime_type: entry.captureType === "video" ? "video/mp4" : "image/jpeg",
441
+ is_video: entry.captureType === "video",
442
+ filePath: finalPath,
443
+ }, finalPath, entry.thumbnailPath);
444
+ recovered.capture_id = entry.captureId;
445
+ recovered.assetReceipt = entry.assetReceipt;
446
+ await localStorageService.saveDownloadedFile(recovered);
447
+ return recovered;
317
448
  }
318
449
  }
319
450
  export const mediaProcessingQueue = new MediaProcessingQueue();
@@ -1 +1 @@
1
- {"version":3,"file":"mediaProcessingQueue.js","sourceRoot":"","sources":["../../../src/services/asg/mediaProcessingQueue.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Media Processing Queue\n *\n * Decouples media processing (HDR merge, color correction, stabilization, camera roll save)\n * from the download pipeline. Downloads push items here; processing runs independently.\n */\n\nimport * as RNFS from \"@dr.pogodin/react-native-fs\"\nimport CrustModule from \"@mentra/crust\"\n\nimport {asgCameraApi} from \"./asgCameraApi\"\nimport {localStorageService} from \"./localStorageService\"\nimport {INVALID_DOWNLOADED_MEDIA, validateDownloadedMediaFile} from \"./galleryMediaValidation\"\nimport {reportInvalidGalleryMedia} from \"./GalleryMediaIntegrityReportService\"\nimport {useGallerySyncStore} from \"../../stores/gallerySync\"\nimport {BgTimer} from \"../../utils/timers\"\nimport {MediaLibraryPermissions} from \"../../utils/permissions/MediaLibraryPermissions\"\n\nconst TAG = \"[MediaProcessingQueue]\"\n\nexport interface ProcessingItem {\n /** Unique ID — capture_id (v2) or file name (v1) */\n id: string\n type: \"photo\" | \"video\"\n /** Path to the primary downloaded file */\n primaryPath: string\n /** HDR bracket paths (v2 only) */\n bracketPaths?: string[]\n /** IMU sidecar path for video stabilization */\n sidecarPath?: string\n /** Thumbnail base64 data */\n thumbnailData?: string\n /** Capture directory for saving thumbnail */\n captureDir?: string\n /** Original capture timestamp */\n timestamp?: number\n /** Total file size */\n totalSize: number\n /** Video duration in ms */\n duration?: number\n /** Glasses model */\n glassesModel?: string\n /** Whether to process (lens/color/stabilization) */\n shouldProcess: boolean\n /** Whether to auto-save to camera roll */\n shouldAutoSave: boolean\n /** Pre-downloaded thumbnail path (v1 legacy sync) */\n thumbnailPath?: string\n /** File names to delete from glasses after processing completes */\n deleteFromGlasses?: string[]\n}\n\nclass MediaProcessingQueue {\n private queue: ProcessingItem[] = []\n private isRunning = false\n private aborted = false\n private generation = 0 // C2: incremented on reset() to invalidate stale processLoop()\n\n /** Add an item to the processing queue. Starts processing if not already running. */\n enqueue(item: ProcessingItem): void {\n this.queue.push(item)\n console.log(`${TAG} Enqueued ${item.id} (${item.type}), queue size: ${this.queue.length}`)\n\n // Only mark as processing if we'll actually process this item\n if (item.shouldProcess) {\n const store = useGallerySyncStore.getState()\n store.onFileProcessing(item.id)\n }\n\n if (!this.isRunning) {\n this.processLoop()\n }\n }\n\n /** Cancel all pending processing. */\n abort(): void {\n this.aborted = true\n this.queue = []\n console.log(`${TAG} Aborted`)\n }\n\n /** Reset state for a new sync session. */\n reset(): void {\n this.queue = []\n this.isRunning = false\n this.aborted = false\n this.generation++ // C2: invalidate any running processLoop\n }\n\n /** Returns true if there are items queued or currently processing. */\n get hasPending(): boolean {\n return this.queue.length > 0 || this.isRunning\n }\n\n /** Returns a promise that resolves when the queue is fully drained, or rejects on timeout. */\n waitUntilDrained(timeoutMs: number = 600000): Promise<void> {\n if (!this.hasPending) return Promise.resolve()\n return new Promise((resolve, reject) => {\n const deadline = Date.now() + timeoutMs\n const check = () => {\n if (!this.hasPending || this.aborted) {\n resolve()\n } else if (Date.now() >= deadline) {\n console.error(`${TAG} waitUntilDrained timed out after ${timeoutMs}ms, aborting queue`)\n this.abort()\n reject(new Error(`Processing queue timed out after ${timeoutMs / 1000}s`))\n } else {\n BgTimer.setTimeout(check, 200)\n }\n }\n check()\n })\n }\n\n /** Process items one at a time. */\n private async processLoop(): Promise<void> {\n if (this.isRunning) return\n this.isRunning = true\n const myGeneration = this.generation // C2: capture generation to detect reset()\n\n while (this.queue.length > 0 && !this.aborted && this.generation === myGeneration) {\n const item = this.queue.shift()!\n let processFailed = false\n try {\n await this.processItem(item)\n } catch (error) {\n console.error(`${TAG} Error processing ${item.id}:`, error)\n processFailed = true\n }\n\n // Exit if generation changed (reset was called during processing)\n if (this.generation !== myGeneration) break\n\n const store = useGallerySyncStore.getState()\n if (!processFailed) {\n // Success: increment processedFiles and remove from processingFiles set.\n store.onFileProcessed(item.id)\n } else {\n // Failure: processItem already called onFileFailed. Calling onFileProcessed\n // here would double-count the item (counted as both failed AND processed).\n // Still clear the processingFiles entry so the UI doesn't show it as in-progress.\n const current = store.processingFiles\n if (current.has(item.id)) {\n const newSet = new Set(current)\n newSet.delete(item.id)\n useGallerySyncStore.setState({processingFiles: newSet})\n }\n }\n }\n\n // Only clear isRunning if this loop owns the current generation\n if (this.generation === myGeneration) {\n this.isRunning = false\n }\n }\n\n /** Process a single item through the full pipeline. */\n private async processItem(item: ProcessingItem): Promise<void> {\n const startTime = Date.now()\n let filePathToSave = item.primaryPath\n\n // 1. HDR merge (photos with brackets only)\n if (item.shouldProcess && item.type === \"photo\" && item.bracketPaths && item.bracketPaths.length >= 3) {\n try {\n const underPath = item.bracketPaths.find((p) => p.includes(\"ev-2\")) || item.bracketPaths[0]\n const normalPath = item.bracketPaths.find((p) => p.includes(\"ev0\")) || item.bracketPaths[1]\n const overPath = item.bracketPaths.find((p) => p.includes(\"ev2\") && !p.includes(\"ev-2\")) || item.bracketPaths[2]\n\n const hdrPath = item.primaryPath + \".hdr.jpg\"\n const hdrResult = await CrustModule.mergeHdrBrackets(underPath, normalPath, overPath, hdrPath)\n if (hdrResult.success && hdrResult.outputPath) {\n filePathToSave = hdrResult.outputPath\n console.log(`${TAG} HDR merged ${item.id} in ${hdrResult.processingTimeMs}ms`)\n }\n } catch (hdrError) {\n console.warn(`${TAG} HDR merge error for ${item.id}, continuing:`, hdrError)\n }\n }\n\n // 2. Image processing (lens + color correction)\n if (item.shouldProcess && item.type === \"photo\") {\n try {\n const processedPath = filePathToSave + \".processed.jpg\"\n const result = await CrustModule.processGalleryImage(filePathToSave, processedPath, {\n lensCorrection: true,\n colorCorrection: true,\n })\n if (result.success && result.outputPath) {\n filePathToSave = result.outputPath\n console.log(`${TAG} 🎨 Processed ${item.id} in ${result.processingTimeMs}ms`)\n }\n } catch (procError) {\n console.warn(`${TAG} Processing error for ${item.id}, using original:`, procError)\n }\n }\n\n // 3. Video stabilization + color correction\n if (item.shouldProcess && item.type === \"video\" && item.sidecarPath) {\n try {\n const stabilizedPath = item.primaryPath + \".stabilized.mp4\"\n const result = await CrustModule.stabilizeVideo(item.primaryPath, item.sidecarPath, stabilizedPath)\n if (result.success && result.outputPath) {\n filePathToSave = result.outputPath\n console.log(`${TAG} 📹 Stabilized ${item.id} in ${result.processingTimeMs}ms`)\n }\n } catch (stabError) {\n console.warn(`${TAG} Stabilization error for ${item.id}, using original:`, stabError)\n }\n }\n\n // 4. Save thumbnail to disk (or use pre-downloaded thumbnail from v1 sync)\n let localThumbnailPath: string | undefined = item.thumbnailPath\n if (item.thumbnailData && item.captureDir) {\n try {\n const thumbPath = `${item.captureDir}/.thumb.jpg`\n const base64Data = item.thumbnailData.startsWith(\"data:\")\n ? item.thumbnailData.split(\",\")[1]\n : item.thumbnailData\n await RNFS.writeFile(thumbPath, base64Data, \"base64\")\n localThumbnailPath = thumbPath\n } catch (thumbError) {\n console.warn(`${TAG} Failed to save thumbnail for ${item.id}:`, thumbError)\n }\n }\n\n // 5. Validate local file before persisting metadata or deleting from glasses\n try {\n await validateDownloadedMediaFile({\n path: filePathToSave,\n name: item.id,\n mediaKind: item.type,\n })\n } catch (validationError: any) {\n const reason =\n validationError?.message?.includes(INVALID_DOWNLOADED_MEDIA) ?\n validationError.message\n : `${INVALID_DOWNLOADED_MEDIA}: ${item.id}`\n console.error(`${TAG} Validation failed for ${item.id}: ${reason}`)\n reportInvalidGalleryMedia({\n name: item.id,\n path: filePathToSave,\n mediaKind: item.type,\n stage: \"processing_validation\",\n reason,\n expectedSize: item.totalSize,\n duration: item.duration,\n glassesModel: item.glassesModel,\n })\n const store = useGallerySyncStore.getState()\n store.onFileFailed(item.id, reason)\n await RNFS.unlink(filePathToSave).catch(() => {})\n for (const intermediate of [\n item.primaryPath + \".hdr.jpg\",\n item.primaryPath + \".processed.jpg\",\n item.primaryPath + \".stabilized.mp4\",\n ]) {\n if (intermediate !== filePathToSave) {\n await RNFS.unlink(intermediate).catch(() => {})\n }\n }\n throw new Error(reason)\n }\n\n // 6. Save to camera roll\n if (item.shouldAutoSave) {\n const success = await MediaLibraryPermissions.saveToLibrary(filePathToSave, item.timestamp)\n if (success) {\n console.log(`${TAG} ✅ Saved to camera roll: ${item.id}`)\n } else {\n console.warn(`${TAG} ❌ Failed to save to camera roll: ${item.id}`)\n // D1: Surface camera roll save failures to the UI\n const store = useGallerySyncStore.getState()\n store.onFileFailed(item.id, \"camera roll save failed\")\n }\n }\n\n // 7. Save metadata\n const isVideo = item.type === \"video\"\n const downloadedFile = localStorageService.convertToDownloadedFile(\n {\n name: item.id,\n url: \"\",\n download: \"\",\n size: item.totalSize,\n modified: item.timestamp || Date.now(),\n is_video: isVideo,\n thumbnail_data: item.thumbnailData,\n duration: item.duration,\n filePath: filePathToSave,\n glassesModel: item.glassesModel,\n },\n filePathToSave,\n localThumbnailPath,\n item.glassesModel,\n )\n try {\n await localStorageService.saveDownloadedFile(downloadedFile)\n } catch (metadataError) {\n // D2: Clean up orphaned file if metadata save fails, then re-throw\n console.error(`${TAG} Metadata save failed for ${item.id}, cleaning up file:`, metadataError)\n await RNFS.unlink(filePathToSave).catch(() => {})\n throw metadataError\n }\n\n // S4: Clean up intermediate processing files\n const intermediates = [\n item.primaryPath + \".hdr.jpg\",\n item.primaryPath + \".processed.jpg\",\n item.primaryPath + \".stabilized.mp4\",\n ]\n for (const intermediate of intermediates) {\n if (intermediate !== filePathToSave) {\n RNFS.unlink(intermediate).catch(() => {}) // fire-and-forget\n }\n }\n\n // 8. Update file in sync queue with local paths for gallery display\n const store = useGallerySyncStore.getState()\n const localFileUrl = filePathToSave.startsWith(\"file://\") ? filePathToSave : `file://${filePathToSave}`\n const localThumbUrl = localThumbnailPath\n ? localThumbnailPath.startsWith(\"file://\")\n ? localThumbnailPath\n : `file://${localThumbnailPath}`\n : undefined\n store.updateFileInQueue(item.id, {\n name: item.id,\n url: localFileUrl,\n download: localFileUrl,\n size: item.totalSize,\n modified: item.timestamp || Date.now(),\n is_video: isVideo,\n filePath: filePathToSave,\n mime_type: isVideo ? \"video/mp4\" : \"image/jpeg\",\n thumbnail_data: item.thumbnailData,\n thumbnailPath: localThumbUrl,\n duration: item.duration,\n })\n\n // 9. Delete from glasses now that processing is complete — but only if the\n // local file actually exists and has data. If the download was truncated or\n // processing failed silently, we must not destroy the only good copy.\n if (item.deleteFromGlasses && item.deleteFromGlasses.length > 0) {\n try {\n const localFileExists = await RNFS.exists(filePathToSave)\n if (!localFileExists) {\n console.error(`${TAG} Skipping glasses deletion for ${item.id}: local file missing at ${filePathToSave}`)\n } else {\n const localStat = await RNFS.stat(filePathToSave)\n if (localStat.size === 0) {\n console.error(`${TAG} Skipping glasses deletion for ${item.id}: local file is 0 bytes`)\n } else if (item.totalSize > 0 && localStat.size < item.totalSize * 0.5) {\n // If local file is less than 50% of expected size, something went wrong\n console.error(\n `${TAG} Skipping glasses deletion for ${item.id}: local file ${localStat.size} bytes is much smaller than expected ${item.totalSize} bytes`,\n )\n } else {\n await asgCameraApi.deleteFilesFromServer(item.deleteFromGlasses)\n console.log(`${TAG} 🗑️ Deleted ${item.deleteFromGlasses.join(\", \")} from glasses`)\n }\n }\n } catch (deleteError) {\n console.warn(`${TAG} Delete from glasses failed for ${item.id} (non-fatal):`, deleteError)\n }\n }\n\n const elapsed = Date.now() - startTime\n console.log(`${TAG} ✅ Finished ${item.id} in ${elapsed}ms`)\n }\n}\n\nexport const mediaProcessingQueue = new MediaProcessingQueue()\n"]}
1
+ {"version":3,"file":"mediaProcessingQueue.js","sourceRoot":"","sources":["../../../src/services/asg/mediaProcessingQueue.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Media Processing Queue\n *\n * Decouples media processing (HDR merge, color correction, stabilization, camera roll save)\n * from the download pipeline. Downloads push items here; processing runs independently.\n */\n\nimport * as RNFS from \"@dr.pogodin/react-native-fs\"\nimport CrustModule from \"@mentra/crust\"\n\nimport {asgCameraApi} from \"./asgCameraApi\"\nimport {localStorageService} from \"./localStorageService\"\nimport {INVALID_DOWNLOADED_MEDIA, validateDownloadedMediaFile} from \"./galleryMediaValidation\"\nimport {reportInvalidGalleryMedia} from \"./GalleryMediaIntegrityReportService\"\nimport {useGallerySyncStore} from \"../../stores/gallerySync\"\nimport {BgTimer} from \"../../utils/timers\"\nimport {galleryTransferLedger, GalleryTransferEntry} from \"./galleryTransferLedger\"\nimport {cameraRollExportCoordinator} from \"./cameraRollExportCoordinator\"\n\nconst TAG = \"[MediaProcessingQueue]\"\n\nexport interface ProcessingItem {\n /** Unique ID — capture_id (v2) or file name (v1) */\n id: string\n type: \"photo\" | \"video\"\n /** Path to the primary downloaded file */\n primaryPath: string\n /** HDR bracket paths (v2 only) */\n bracketPaths?: string[]\n /** IMU sidecar path for video stabilization */\n sidecarPath?: string\n /** Thumbnail base64 data */\n thumbnailData?: string\n /** Capture directory for saving thumbnail */\n captureDir?: string\n /** Original capture timestamp */\n timestamp?: number\n /** Total file size */\n totalSize: number\n /** Video duration in ms */\n duration?: number\n /** Glasses model */\n glassesModel?: string\n /** Whether to process (lens/color/stabilization) */\n shouldProcess: boolean\n /** Whether to auto-save to camera roll */\n shouldAutoSave: boolean\n /** Pre-downloaded thumbnail path (v1 legacy sync) */\n thumbnailPath?: string\n /** File names to delete from glasses after processing completes */\n deleteFromGlasses?: string[]\n /** Negotiated gallery protocol; absent entries are inferred from the transfer ledger. */\n protocolVersion?: number\n}\n\nclass MediaProcessingQueue {\n private queue: ProcessingItem[] = []\n private isRunning = false\n private aborted = false\n private generation = 0 // C2: incremented on reset() to invalidate stale processLoop()\n\n /** Add an item to the processing queue. Starts processing if not already running. */\n enqueue(item: ProcessingItem): void {\n this.queue.push(item)\n console.log(`${TAG} Enqueued ${item.id} (${item.type}), queue size: ${this.queue.length}`)\n\n // Only mark as processing if we'll actually process this item\n if (item.shouldProcess) {\n const store = useGallerySyncStore.getState()\n store.onFileProcessing(item.id)\n }\n\n if (!this.isRunning) {\n this.processLoop()\n }\n }\n\n /** Cancel all pending processing. */\n abort(): void {\n this.aborted = true\n this.queue = []\n console.log(`${TAG} Aborted`)\n }\n\n /** Reset state for a new sync session. */\n reset(): void {\n this.queue = []\n this.isRunning = false\n this.aborted = false\n this.generation++ // C2: invalidate any running processLoop\n }\n\n /** Returns true if there are items queued or currently processing. */\n get hasPending(): boolean {\n return this.queue.length > 0 || this.isRunning\n }\n\n /** Returns a promise that resolves when the queue is fully drained, or rejects on timeout. */\n waitUntilDrained(timeoutMs: number = 4 * 60 * 60 * 1000): Promise<void> {\n if (!this.hasPending) return Promise.resolve()\n return new Promise((resolve, reject) => {\n const deadline = Date.now() + timeoutMs\n const check = () => {\n if (!this.hasPending || this.aborted) {\n resolve()\n } else if (Date.now() >= deadline) {\n console.error(`${TAG} waitUntilDrained timed out after ${timeoutMs}ms, aborting queue`)\n this.abort()\n reject(new Error(`Processing queue timed out after ${timeoutMs / 1000}s`))\n } else {\n BgTimer.setTimeout(check, 200)\n }\n }\n check()\n })\n }\n\n /** Retry durable export/ack work after an app restart without downloading media again. */\n async retryPending(): Promise<{retried: number; failed: number}> {\n let retried = 0\n let failed = 0\n for (let entry of galleryTransferLedger.pendingRecovery()) {\n try {\n if (entry.state === \"RESTORE_PENDING\") {\n await asgCameraApi.restoreCapture(entry.captureId)\n galleryTransferLedger.transition(entry.captureId, \"DISCOVERED\", {lastError: undefined})\n retried++\n continue\n }\n if (!entry.finalPath || !(await RNFS.exists(entry.finalPath))) {\n throw new Error(`Recovery file is missing for ${entry.captureId}`)\n }\n // Rebuild a missing app index before any source acknowledgement, even when camera-roll\n // saving is disabled or an export receipt was already committed before restart.\n const downloadedFile = await this.recoverDownloadedFile(entry)\n if (entry.state === \"EXPORT_PENDING\" || entry.state === \"INDEXED\") {\n const shouldAutoSave = await cameraRollExportCoordinator.isAutoSaveEnabled()\n entry = galleryTransferLedger.update(entry.captureId, {shouldAutoSave, lastError: undefined})\n if (shouldAutoSave) {\n if (entry.state !== \"EXPORT_PENDING\") {\n entry = galleryTransferLedger.transition(entry.captureId, \"EXPORT_PENDING\")\n }\n const receipt = await cameraRollExportCoordinator.exportForSource(downloadedFile, entry.captureId)\n entry = receipt\n ? galleryTransferLedger.transition(entry.captureId, \"EXPORTED\", {assetReceipt: receipt})\n : galleryTransferLedger.transition(entry.captureId, \"INDEXED\", {\n shouldAutoSave: false,\n lastError: undefined,\n })\n } else if (entry.state === \"EXPORT_PENDING\") {\n entry = galleryTransferLedger.transition(entry.captureId, \"INDEXED\", {\n shouldAutoSave: false,\n lastError: undefined,\n })\n }\n }\n entry = galleryTransferLedger.transition(entry.captureId, \"ACK_PENDING\")\n await this.acknowledgeEntry(entry)\n galleryTransferLedger.transition(entry.captureId, \"TRASHED\", {lastError: undefined})\n retried++\n } catch (error) {\n galleryTransferLedger.recordFailure(entry.captureId, error)\n failed++\n console.warn(`${TAG} Recovery remains pending for ${entry.captureId}:`, error)\n }\n }\n return {retried, failed}\n }\n\n private async acknowledgeEntry(entry: GalleryTransferEntry): Promise<void> {\n if (entry.protocolVersion >= 3) {\n await asgCameraApi.acknowledgeCapture(entry.captureId, entry.ackId)\n return\n }\n const result = await asgCameraApi.deleteFilesFromServer(\n entry.deleteFromGlasses.length > 0 ? entry.deleteFromGlasses : [entry.captureId],\n )\n if (result.failed.length > 0 || result.deleted.length === 0) {\n throw new Error(`Glasses did not acknowledge ${entry.captureId}`)\n }\n }\n\n /** Process items one at a time. */\n private async processLoop(): Promise<void> {\n if (this.isRunning) return\n this.isRunning = true\n const myGeneration = this.generation // C2: capture generation to detect reset()\n\n while (this.queue.length > 0 && !this.aborted && this.generation === myGeneration) {\n const item = this.queue.shift()!\n let processFailed = false\n try {\n await this.processItem(item)\n } catch (error) {\n console.error(`${TAG} Error processing ${item.id}:`, error)\n processFailed = true\n const store = useGallerySyncStore.getState()\n if (!store.failedFiles.includes(item.id)) {\n store.onFileFailed(item.id, error instanceof Error ? error.message : String(error))\n }\n }\n\n // Exit if generation changed (reset was called during processing)\n if (this.generation !== myGeneration) break\n\n const store = useGallerySyncStore.getState()\n if (!processFailed) {\n // Success: increment processedFiles and remove from processingFiles set.\n store.onFileProcessed(item.id)\n } else {\n // Failure is counted exactly once above. Still clear the processingFiles entry so the UI\n // does not show it as in-progress.\n const current = store.processingFiles\n if (current.has(item.id)) {\n const newSet = new Set(current)\n newSet.delete(item.id)\n useGallerySyncStore.setState({processingFiles: newSet})\n }\n }\n }\n\n // Only clear isRunning if this loop owns the current generation\n if (this.generation === myGeneration) {\n this.isRunning = false\n }\n }\n\n /** Process a single item through the full pipeline. */\n private async processItem(item: ProcessingItem): Promise<void> {\n const startTime = Date.now()\n let filePathToSave = item.primaryPath\n let ledgerEntry = galleryTransferLedger.get(item.id)\n if (!ledgerEntry) {\n ledgerEntry = galleryTransferLedger.ensureCapture(\n {\n capture_id: item.id,\n type: item.type,\n timestamp: item.timestamp || Date.now(),\n total_size: item.totalSize,\n files: (item.deleteFromGlasses?.length ? item.deleteFromGlasses : [item.id]).map((name, index) => ({\n name,\n size: index === 0 ? item.totalSize : 0,\n role: index === 0 ? \"primary\" : \"sidecar\",\n })),\n },\n item.protocolVersion || 2,\n )\n }\n\n // 1. HDR merge (photos with brackets only)\n if (item.shouldProcess && item.type === \"photo\" && item.bracketPaths && item.bracketPaths.length >= 3) {\n try {\n const underPath = item.bracketPaths.find((p) => p.includes(\"ev-2\")) || item.bracketPaths[0]\n const normalPath = item.bracketPaths.find((p) => p.includes(\"ev0\")) || item.bracketPaths[1]\n const overPath = item.bracketPaths.find((p) => p.includes(\"ev2\") && !p.includes(\"ev-2\")) || item.bracketPaths[2]\n\n const hdrPath = item.primaryPath + \".hdr.jpg\"\n const hdrResult = await CrustModule.mergeHdrBrackets(underPath, normalPath, overPath, hdrPath)\n if (hdrResult.success && hdrResult.outputPath) {\n filePathToSave = hdrResult.outputPath\n console.log(`${TAG} HDR merged ${item.id} in ${hdrResult.processingTimeMs}ms`)\n }\n } catch (hdrError) {\n console.warn(`${TAG} HDR merge error for ${item.id}, continuing:`, hdrError)\n }\n }\n\n // 2. Image processing (lens + color correction)\n if (item.shouldProcess && item.type === \"photo\") {\n try {\n const processedPath = filePathToSave + \".processed.jpg\"\n const result = await CrustModule.processGalleryImage(filePathToSave, processedPath, {\n lensCorrection: true,\n colorCorrection: true,\n })\n if (result.success && result.outputPath) {\n filePathToSave = result.outputPath\n console.log(`${TAG} 🎨 Processed ${item.id} in ${result.processingTimeMs}ms`)\n }\n } catch (procError) {\n console.warn(`${TAG} Processing error for ${item.id}, using original:`, procError)\n }\n }\n\n // 3. Video stabilization + color correction\n if (item.shouldProcess && item.type === \"video\" && item.sidecarPath) {\n try {\n const stabilizedPath = item.primaryPath + \".stabilized.mp4\"\n const result = await CrustModule.stabilizeVideo(item.primaryPath, item.sidecarPath, stabilizedPath)\n if (result.success && result.outputPath) {\n filePathToSave = result.outputPath\n console.log(`${TAG} 📹 Stabilized ${item.id} in ${result.processingTimeMs}ms`)\n }\n } catch (stabError) {\n console.warn(`${TAG} Stabilization error for ${item.id}, using original:`, stabError)\n }\n }\n\n // 4. Save thumbnail to disk (or use pre-downloaded thumbnail from v1 sync)\n let localThumbnailPath: string | undefined = item.thumbnailPath\n if (item.thumbnailData && item.captureDir) {\n try {\n const thumbPath = `${item.captureDir}/.thumb.jpg`\n const base64Data = item.thumbnailData.startsWith(\"data:\")\n ? item.thumbnailData.split(\",\")[1]\n : item.thumbnailData\n await RNFS.writeFile(thumbPath, base64Data, \"base64\")\n localThumbnailPath = thumbPath\n } catch (thumbError) {\n console.warn(`${TAG} Failed to save thumbnail for ${item.id}:`, thumbError)\n }\n }\n\n // 5. Validate local file before persisting metadata or deleting from glasses\n try {\n await validateDownloadedMediaFile({\n path: filePathToSave,\n name: item.id,\n mediaKind: item.type,\n })\n } catch (validationError: any) {\n const reason = validationError?.message?.includes(INVALID_DOWNLOADED_MEDIA)\n ? validationError.message\n : `${INVALID_DOWNLOADED_MEDIA}: ${item.id}`\n console.error(`${TAG} Validation failed for ${item.id}: ${reason}`)\n reportInvalidGalleryMedia({\n name: item.id,\n path: filePathToSave,\n mediaKind: item.type,\n stage: \"processing_validation\",\n reason,\n expectedSize: item.totalSize,\n duration: item.duration,\n glassesModel: item.glassesModel,\n })\n const store = useGallerySyncStore.getState()\n store.onFileFailed(item.id, reason)\n galleryTransferLedger.recordFailure(item.id, validationError)\n throw new Error(reason)\n }\n\n // 6. Commit the app-local index before any export or source acknowledgement. Use the final\n // on-disk output metadata for camera-roll identity: processing can change byte size, while\n // recovery reconstructs this row from the same file and the transfer ledger timestamp.\n const isVideo = item.type === \"video\"\n const finalFileStat = await RNFS.stat(filePathToSave)\n const finalFileSize = Number(finalFileStat.size)\n const downloadedFile = localStorageService.convertToDownloadedFile(\n {\n name: item.id,\n url: \"\",\n download: \"\",\n size: finalFileSize,\n modified: ledgerEntry.timestamp,\n is_video: isVideo,\n thumbnail_data: item.thumbnailData,\n duration: item.duration,\n filePath: filePathToSave,\n glassesModel: item.glassesModel,\n },\n filePathToSave,\n localThumbnailPath,\n item.glassesModel,\n )\n downloadedFile.capture_id = item.id\n const shouldAutoSave = await cameraRollExportCoordinator.isAutoSaveEnabled()\n try {\n await localStorageService.saveDownloadedFile(downloadedFile)\n ledgerEntry = galleryTransferLedger.transition(item.id, \"INDEXED\", {\n finalPath: filePathToSave,\n thumbnailPath: localThumbnailPath,\n shouldAutoSave,\n })\n await cameraRollExportCoordinator.recordIndexed(\n downloadedFile,\n shouldAutoSave,\n shouldAutoSave ? \"SOURCE_BARRIER\" : \"HISTORICAL_BACKFILL\",\n item.id,\n )\n } catch (metadataError) {\n console.error(`${TAG} Metadata save failed for ${item.id}; retaining local and glasses copies:`, metadataError)\n galleryTransferLedger.recordFailure(item.id, metadataError)\n throw metadataError\n }\n\n // 7. While enabled, export is a durable, receipted step whose failures block ack. Turning\n // auto-save off safely falls back to the already-verified app-local copy and later backfill.\n if (shouldAutoSave) {\n galleryTransferLedger.transition(item.id, \"EXPORT_PENDING\")\n let receipt\n try {\n receipt = await cameraRollExportCoordinator.exportForSource(downloadedFile, item.id)\n } catch (error) {\n galleryTransferLedger.recordFailure(item.id, error)\n throw error\n }\n if (receipt) {\n ledgerEntry = galleryTransferLedger.transition(item.id, \"EXPORTED\", {\n assetReceipt: receipt,\n })\n console.log(`${TAG} ✅ Saved to camera roll with receipt: ${item.id}`)\n } else {\n ledgerEntry = galleryTransferLedger.transition(item.id, \"INDEXED\", {\n shouldAutoSave: false,\n lastError: undefined,\n })\n console.log(`${TAG} Automatic camera-roll saving was disabled; keeping app-local copy: ${item.id}`)\n }\n }\n\n // S4: Clean up intermediate processing files\n const intermediates = [\n item.primaryPath + \".hdr.jpg\",\n item.primaryPath + \".processed.jpg\",\n item.primaryPath + \".stabilized.mp4\",\n ]\n for (const intermediate of intermediates) {\n if (intermediate !== filePathToSave) {\n RNFS.unlink(intermediate).catch(() => {}) // fire-and-forget\n }\n }\n\n // 8. Update file in sync queue with local paths for gallery display\n const store = useGallerySyncStore.getState()\n const localFileUrl = filePathToSave.startsWith(\"file://\") ? filePathToSave : `file://${filePathToSave}`\n const localThumbUrl = localThumbnailPath\n ? localThumbnailPath.startsWith(\"file://\")\n ? localThumbnailPath\n : `file://${localThumbnailPath}`\n : undefined\n store.updateFileInQueue(item.id, {\n name: item.id,\n url: localFileUrl,\n download: localFileUrl,\n size: finalFileSize,\n modified: ledgerEntry.timestamp,\n is_video: isVideo,\n filePath: filePathToSave,\n mime_type: isVideo ? \"video/mp4\" : \"image/jpeg\",\n thumbnail_data: item.thumbnailData,\n thumbnailPath: localThumbUrl,\n duration: item.duration,\n })\n\n // 9. Acknowledge only after VERIFIED -> INDEXED -> (EXPORTED). The server turns this into\n // recoverable trash; failures remain ACK_PENDING and are retried without re-downloading.\n const localFileExists = await RNFS.exists(filePathToSave)\n const localStat = localFileExists ? await RNFS.stat(filePathToSave) : null\n if (!localStat || localStat.size <= 0) {\n const error = new Error(`Cannot acknowledge ${item.id}: committed local file is missing or empty`)\n galleryTransferLedger.recordFailure(item.id, error)\n throw error\n }\n ledgerEntry = galleryTransferLedger.transition(item.id, \"ACK_PENDING\")\n try {\n await this.acknowledgeEntry(ledgerEntry)\n galleryTransferLedger.transition(item.id, \"TRASHED\", {lastError: undefined})\n } catch (ackError) {\n galleryTransferLedger.recordFailure(item.id, ackError)\n throw ackError\n }\n\n const elapsed = Date.now() - startTime\n console.log(`${TAG} ✅ Finished ${item.id} in ${elapsed}ms`)\n }\n\n /** Rebuild a missing app-local index row from the durable transfer ledger. */\n private async recoverDownloadedFile(entry: GalleryTransferEntry) {\n if (!entry.finalPath) throw new Error(`Recovery file is missing for ${entry.captureId}`)\n\n // The transfer ledger is the durable source of truth for a committed generation. A local\n // index row can survive an interrupted replacement and still point at an older/missing path,\n // or contain the pre-processing size. Only reuse it when its path and identity still match the\n // ledger's committed file; otherwise rebuild the row before export or source acknowledgement.\n const finalPath = entry.finalPath.replace(/^file:\\/\\//, \"\")\n const indexed = await localStorageService.getDownloadedFile(entry.captureId)\n if (indexed) {\n try {\n const indexedPath = indexed.filePath?.replace(/^file:\\/\\//, \"\")\n if (indexedPath === finalPath && (await RNFS.exists(indexedPath))) {\n const indexedStat = await RNFS.stat(indexedPath)\n const indexedSize = Number(indexedStat.size)\n if (indexedSize > 0 && indexed.size === indexedSize && indexed.modified === entry.timestamp) {\n return indexed\n }\n }\n } catch (error) {\n console.warn(`${TAG} Ignoring stale local index for ${entry.captureId}:`, error)\n }\n }\n\n const stat = await RNFS.stat(finalPath)\n const finalSize = Number(stat.size)\n if (!Number.isFinite(finalSize) || finalSize <= 0) {\n throw new Error(`Recovery file is missing or empty for ${entry.captureId}`)\n }\n const recovered = localStorageService.convertToDownloadedFile(\n {\n name: entry.captureId,\n url: \"\",\n download: \"\",\n size: finalSize,\n modified: entry.timestamp,\n mime_type: entry.captureType === \"video\" ? \"video/mp4\" : \"image/jpeg\",\n is_video: entry.captureType === \"video\",\n filePath: finalPath,\n },\n finalPath,\n entry.thumbnailPath,\n )\n recovered.capture_id = entry.captureId\n recovered.assetReceipt = entry.assetReceipt\n await localStorageService.saveDownloadedFile(recovered)\n return recovered\n }\n}\n\nexport const mediaProcessingQueue = new MediaProcessingQueue()\n"]}
@@ -0,0 +1,8 @@
1
+ import type { AudioSource } from "expo-audio";
2
+ /**
3
+ * A tiny bundled source used to detach completed or interrupted audio without
4
+ * destroying the reusable native player. If a media control later sends Play,
5
+ * only this silence can be resumed instead of the previous miniapp audio.
6
+ */
7
+ export declare const SILENT_AUDIO_SOURCE: AudioSource;
8
+ //# sourceMappingURL=audioPlaybackAssets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audioPlaybackAssets.d.ts","sourceRoot":"","sources":["../../src/services/audioPlaybackAssets.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,YAAY,CAAA;AAE3C;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,WAA8C,CAAA"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * A tiny bundled source used to detach completed or interrupted audio without
3
+ * destroying the reusable native player. If a media control later sends Play,
4
+ * only this silence can be resumed instead of the previous miniapp audio.
5
+ */
6
+ export const SILENT_AUDIO_SOURCE = require("../assets/silence.wav");
7
+ //# sourceMappingURL=audioPlaybackAssets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audioPlaybackAssets.js","sourceRoot":"","sources":["../../src/services/audioPlaybackAssets.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAgB,OAAO,CAAC,uBAAuB,CAAC,CAAA","sourcesContent":["import type {AudioSource} from \"expo-audio\"\n\n/**\n * A tiny bundled source used to detach completed or interrupted audio without\n * destroying the reusable native player. If a media control later sends Play,\n * only this silence can be resumed instead of the previous miniapp audio.\n */\nexport const SILENT_AUDIO_SOURCE: AudioSource = require(\"../assets/silence.wav\")\n"]}
@@ -13,7 +13,7 @@ export function detectClockSkew(phoneNow, glassesServerTime, lastSyncTime, toler
13
13
  return { skewed: false, reason: "" };
14
14
  }
15
15
  export function isSyncManifestEmpty(syncData) {
16
- if (syncData.api_version === 2 && syncData.captures && syncData.captures.length > 0) {
16
+ if ((syncData.api_version || 0) >= 2 && syncData.captures && syncData.captures.length > 0) {
17
17
  return false;
18
18
  }
19
19
  return !syncData.changed_files || syncData.changed_files.length === 0;
@@ -1 +1 @@
1
- {"version":3,"file":"gallerySyncClock.js","sourceRoot":"","sources":["../../src/services/gallerySyncClock.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,CAAC,MAAM,uBAAuB,GAAG,MAAM,CAAA;AAS7C,MAAM,UAAU,eAAe,CAC7B,QAAgB,EAChB,iBAAyB,EACzB,YAAoB,EACpB,cAAsB,uBAAuB;IAE7C,IAAI,YAAY,GAAG,iBAAiB,GAAG,WAAW,EAAE,CAAC;QACnD,OAAO,EAAC,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,4BAA4B,EAAC,CAAA;IAC7D,CAAC;IACD,IAAI,IAAI,CAAC,GAAG,CAAC,QAAQ,GAAG,iBAAiB,CAAC,GAAG,WAAW,EAAE,CAAC;QACzD,OAAO,EAAC,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,kBAAkB,EAAC,CAAA;IACnD,CAAC;IACD,OAAO,EAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAC,CAAA;AACpC,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,QAInC;IACC,IAAI,QAAQ,CAAC,WAAW,KAAK,CAAC,IAAI,QAAQ,CAAC,QAAQ,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpF,OAAO,KAAK,CAAA;IACd,CAAC;IACD,OAAO,CAAC,QAAQ,CAAC,aAAa,IAAI,QAAQ,CAAC,aAAa,CAAC,MAAM,KAAK,CAAC,CAAA;AACvE,CAAC","sourcesContent":["/**\n * Clock skew detection for gallery sync (phone vs glasses).\n * Phone pushes time to glasses only when skew is detected during sync.\n */\n\nexport const CLOCK_SKEW_TOLERANCE_MS = 60_000\n\nexport type ClockSkewReason = \"watermark_ahead_of_glasses\" | \"wall_clock_drift\" | \"\"\n\nexport interface ClockSkewResult {\n skewed: boolean\n reason: ClockSkewReason\n}\n\nexport function detectClockSkew(\n phoneNow: number,\n glassesServerTime: number,\n lastSyncTime: number,\n toleranceMs: number = CLOCK_SKEW_TOLERANCE_MS,\n): ClockSkewResult {\n if (lastSyncTime > glassesServerTime + toleranceMs) {\n return {skewed: true, reason: \"watermark_ahead_of_glasses\"}\n }\n if (Math.abs(phoneNow - glassesServerTime) > toleranceMs) {\n return {skewed: true, reason: \"wall_clock_drift\"}\n }\n return {skewed: false, reason: \"\"}\n}\n\nexport function isSyncManifestEmpty(syncData: {\n api_version?: number\n captures?: unknown[]\n changed_files?: unknown[]\n}): boolean {\n if (syncData.api_version === 2 && syncData.captures && syncData.captures.length > 0) {\n return false\n }\n return !syncData.changed_files || syncData.changed_files.length === 0\n}\n"]}
1
+ {"version":3,"file":"gallerySyncClock.js","sourceRoot":"","sources":["../../src/services/gallerySyncClock.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,CAAC,MAAM,uBAAuB,GAAG,MAAM,CAAA;AAS7C,MAAM,UAAU,eAAe,CAC7B,QAAgB,EAChB,iBAAyB,EACzB,YAAoB,EACpB,cAAsB,uBAAuB;IAE7C,IAAI,YAAY,GAAG,iBAAiB,GAAG,WAAW,EAAE,CAAC;QACnD,OAAO,EAAC,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,4BAA4B,EAAC,CAAA;IAC7D,CAAC;IACD,IAAI,IAAI,CAAC,GAAG,CAAC,QAAQ,GAAG,iBAAiB,CAAC,GAAG,WAAW,EAAE,CAAC;QACzD,OAAO,EAAC,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,kBAAkB,EAAC,CAAA;IACnD,CAAC;IACD,OAAO,EAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAC,CAAA;AACpC,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,QAInC;IACC,IAAI,CAAC,QAAQ,CAAC,WAAW,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,QAAQ,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1F,OAAO,KAAK,CAAA;IACd,CAAC;IACD,OAAO,CAAC,QAAQ,CAAC,aAAa,IAAI,QAAQ,CAAC,aAAa,CAAC,MAAM,KAAK,CAAC,CAAA;AACvE,CAAC","sourcesContent":["/**\n * Clock skew detection for gallery sync (phone vs glasses).\n * Phone pushes time to glasses only when skew is detected during sync.\n */\n\nexport const CLOCK_SKEW_TOLERANCE_MS = 60_000\n\nexport type ClockSkewReason = \"watermark_ahead_of_glasses\" | \"wall_clock_drift\" | \"\"\n\nexport interface ClockSkewResult {\n skewed: boolean\n reason: ClockSkewReason\n}\n\nexport function detectClockSkew(\n phoneNow: number,\n glassesServerTime: number,\n lastSyncTime: number,\n toleranceMs: number = CLOCK_SKEW_TOLERANCE_MS,\n): ClockSkewResult {\n if (lastSyncTime > glassesServerTime + toleranceMs) {\n return {skewed: true, reason: \"watermark_ahead_of_glasses\"}\n }\n if (Math.abs(phoneNow - glassesServerTime) > toleranceMs) {\n return {skewed: true, reason: \"wall_clock_drift\"}\n }\n return {skewed: false, reason: \"\"}\n}\n\nexport function isSyncManifestEmpty(syncData: {\n api_version?: number\n captures?: unknown[]\n changed_files?: unknown[]\n}): boolean {\n if ((syncData.api_version || 0) >= 2 && syncData.captures && syncData.captures.length > 0) {\n return false\n }\n return !syncData.changed_files || syncData.changed_files.length === 0\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"glassesClockSync.d.ts","sourceRoot":"","sources":["../../src/services/glassesClockSync.ts"],"names":[],"mappings":"AAAA;;GAEG;AASH,eAAO,MAAM,eAAe,MAAM,CAAA;AASlC,0BAA0B;AAC1B,wBAAgB,gCAAgC,IAAI,IAAI,CAGvD;AAMD;;GAEG;AACH,wBAAsB,uBAAuB,CAAC,iBAAiB,EAAE,MAAM,EAAE,YAAY,SAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAS3G;AAED;;;GAGG;AACH,wBAAsB,6BAA6B,CACjD,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,OAAO,CAAC,CA+ClB;AAED;;GAEG;AACH,wBAAsB,mCAAmC,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,CAW5G"}
1
+ {"version":3,"file":"glassesClockSync.d.ts","sourceRoot":"","sources":["../../src/services/glassesClockSync.ts"],"names":[],"mappings":"AAAA;;GAEG;AASH,eAAO,MAAM,eAAe,MAAM,CAAA;AASlC,0BAA0B;AAC1B,wBAAgB,gCAAgC,IAAI,IAAI,CAGvD;AAMD;;GAEG;AACH,wBAAsB,uBAAuB,CAAC,iBAAiB,EAAE,MAAM,EAAE,YAAY,SAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAS3G;AAED;;;GAGG;AACH,wBAAsB,6BAA6B,CACjD,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,OAAO,CAAC,CAmDlB;AAED;;GAEG;AACH,wBAAsB,mCAAmC,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,CAW5G"}
@@ -4,7 +4,7 @@
4
4
  import BluetoothSdk from "@mentra/bluetooth-sdk/internal";
5
5
  import { BgTimer } from "../utils/timers";
6
6
  import { useGlassesStore } from "../stores/glasses";
7
- import { getAsgOtaVersionUrl } from "./asgOtaVersionUrl";
7
+ import { resolveOtaManifestUrl } from "./otaManifestUrl";
8
8
  import { detectClockSkew } from "./gallerySyncClock";
9
9
  export const CLOCK_SETTLE_MS = 500;
10
10
  const OTA_CLOCK_FIX_COOLDOWN_MS = 30_000;
@@ -70,7 +70,11 @@ export async function handleOtaClockSkewFromGlasses(errorCode, glassesTimeMs) {
70
70
  }
71
71
  lastOtaClockFixAt = Date.now();
72
72
  const { buildNumber, otaVersionUrl } = useGlassesStore.getState();
73
- const manifestUrl = getAsgOtaVersionUrl(otaVersionUrl, buildNumber);
73
+ const manifestUrl = resolveOtaManifestUrl(otaVersionUrl, buildNumber);
74
+ if (!manifestUrl) {
75
+ console.warn("[GlassesClockSync] OTA restart skipped because this build has no manifest pin");
76
+ return false;
77
+ }
74
78
  console.log("[GlassesClockSync] ⏰ Restarting OTA with ota_start after clock fix");
75
79
  await BluetoothSdk.startOtaUpdate(manifestUrl);
76
80
  return true;
@@ -1 +1 @@
1
- {"version":3,"file":"glassesClockSync.js","sourceRoot":"","sources":["../../src/services/glassesClockSync.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Push phone time to glasses only when clock skew is detected (gallery sync, OTA, etc.).\n */\n\nimport BluetoothSdk from \"@mentra/bluetooth-sdk/internal\"\nimport {BgTimer} from \"../utils/timers\"\nimport {useGlassesStore} from \"../stores/glasses\"\n\nimport {getAsgOtaVersionUrl} from \"./asgOtaVersionUrl\"\nimport {detectClockSkew} from \"./gallerySyncClock\"\n\nexport const CLOCK_SETTLE_MS = 500\n\nconst OTA_CLOCK_FIX_COOLDOWN_MS = 30_000\nlet lastOtaClockFixAt = 0\n// Coalesce concurrent OTA clock-fix attempts. Glasses can emit two clock_skew events\n// back-to-back during BLE handshake; without this guard both would await the BLE fix\n// + retry pair before either updated `lastOtaClockFixAt`.\nlet inflightOtaClockFix: Promise<boolean> | null = null\n\n/** @internal test-only */\nexport function resetOtaClockFixCooldownForTests(): void {\n lastOtaClockFixAt = 0\n inflightOtaClockFix = null\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => BgTimer.setTimeout(resolve, ms))\n}\n\n/**\n * Set glasses system clock from phone when skew is detected.\n */\nexport async function fixGlassesClockIfSkewed(glassesServerTime: number, lastSyncTime = 0): Promise<boolean> {\n const {skewed, reason} = detectClockSkew(Date.now(), glassesServerTime, lastSyncTime)\n if (!skewed) {\n return false\n }\n console.log(`[GlassesClockSync] ⏰ Clock skew detected (${reason}) — setting glasses time from phone`)\n await BluetoothSdk.setSystemTime(Date.now())\n await delay(CLOCK_SETTLE_MS)\n return true\n}\n\n/**\n * After glasses report OTA failure due to clock skew, fix time and restart\n * through the normal phone-driven ota_start path.\n */\nexport async function handleOtaClockSkewFromGlasses(\n errorCode: string | undefined,\n glassesTimeMs?: number,\n): Promise<boolean> {\n if (Date.now() - lastOtaClockFixAt < OTA_CLOCK_FIX_COOLDOWN_MS) {\n return false\n }\n\n const hasGlassesTime = typeof glassesTimeMs === \"number\" && Number.isFinite(glassesTimeMs) && glassesTimeMs > 0\n\n if (errorCode === \"ssl_error\") {\n if (!hasGlassesTime) {\n return false\n }\n const {skewed} = detectClockSkew(Date.now(), glassesTimeMs, 0)\n if (!skewed) {\n return false\n }\n console.log(\"[GlassesClockSync] ⏰ OTA ssl_error with wall-clock drift — treating as clock skew\")\n } else if (errorCode !== \"clock_skew\") {\n return false\n }\n\n // Coalesce concurrent attempts: if a fix is already in flight, await its result instead\n // of racing it through the cooldown guard.\n if (inflightOtaClockFix) {\n return inflightOtaClockFix\n }\n\n const glassesTime = hasGlassesTime ? glassesTimeMs : Date.now() - 86_400_000\n\n inflightOtaClockFix = (async () => {\n try {\n const fixed = await fixGlassesClockIfSkewed(glassesTime, 0)\n if (!fixed) {\n return false\n }\n\n lastOtaClockFixAt = Date.now()\n const {buildNumber, otaVersionUrl} = useGlassesStore.getState()\n const manifestUrl = getAsgOtaVersionUrl(otaVersionUrl, buildNumber)\n console.log(\"[GlassesClockSync] ⏰ Restarting OTA with ota_start after clock fix\")\n await BluetoothSdk.startOtaUpdate(manifestUrl)\n return true\n } finally {\n inflightOtaClockFix = null\n }\n })()\n\n return inflightOtaClockFix\n}\n\n/**\n * Proactive fix when version_info includes glasses system_time_ms before OTA manifest checks.\n */\nexport async function maybeFixGlassesClockFromVersionInfo(systemTimeMs: number | undefined): Promise<boolean> {\n if (typeof systemTimeMs !== \"number\" || systemTimeMs <= 0) {\n return false\n }\n\n const fixed = await fixGlassesClockIfSkewed(systemTimeMs, 0)\n if (!fixed) {\n return false\n }\n\n return true\n}\n"]}
1
+ {"version":3,"file":"glassesClockSync.js","sourceRoot":"","sources":["../../src/services/glassesClockSync.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,YAAY,MAAM,gCAAgC,CAAA;AACzD,OAAO,EAAC,OAAO,EAAC,MAAM,iBAAiB,CAAA;AACvC,OAAO,EAAC,eAAe,EAAC,MAAM,mBAAmB,CAAA;AAEjD,OAAO,EAAC,qBAAqB,EAAC,MAAM,kBAAkB,CAAA;AACtD,OAAO,EAAC,eAAe,EAAC,MAAM,oBAAoB,CAAA;AAElD,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,CAAA;AAElC,MAAM,yBAAyB,GAAG,MAAM,CAAA;AACxC,IAAI,iBAAiB,GAAG,CAAC,CAAA;AACzB,qFAAqF;AACrF,qFAAqF;AACrF,0DAA0D;AAC1D,IAAI,mBAAmB,GAA4B,IAAI,CAAA;AAEvD,0BAA0B;AAC1B,MAAM,UAAU,gCAAgC;IAC9C,iBAAiB,GAAG,CAAC,CAAA;IACrB,mBAAmB,GAAG,IAAI,CAAA;AAC5B,CAAC;AAED,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAA;AAClE,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,iBAAyB,EAAE,YAAY,GAAG,CAAC;IACvF,MAAM,EAAC,MAAM,EAAE,MAAM,EAAC,GAAG,eAAe,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,iBAAiB,EAAE,YAAY,CAAC,CAAA;IACrF,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,KAAK,CAAA;IACd,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,6CAA6C,MAAM,qCAAqC,CAAC,CAAA;IACrG,MAAM,YAAY,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAA;IAC5C,MAAM,KAAK,CAAC,eAAe,CAAC,CAAA;IAC5B,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,6BAA6B,CACjD,SAA6B,EAC7B,aAAsB;IAEtB,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,iBAAiB,GAAG,yBAAyB,EAAE,CAAC;QAC/D,OAAO,KAAK,CAAA;IACd,CAAC;IAED,MAAM,cAAc,GAAG,OAAO,aAAa,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,aAAa,GAAG,CAAC,CAAA;IAE/G,IAAI,SAAS,KAAK,WAAW,EAAE,CAAC;QAC9B,IAAI,CAAC,cAAc,EAAE,CAAC;YACpB,OAAO,KAAK,CAAA;QACd,CAAC;QACD,MAAM,EAAC,MAAM,EAAC,GAAG,eAAe,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,CAAC,CAAC,CAAA;QAC9D,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,KAAK,CAAA;QACd,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,mFAAmF,CAAC,CAAA;IAClG,CAAC;SAAM,IAAI,SAAS,KAAK,YAAY,EAAE,CAAC;QACtC,OAAO,KAAK,CAAA;IACd,CAAC;IAED,wFAAwF;IACxF,2CAA2C;IAC3C,IAAI,mBAAmB,EAAE,CAAC;QACxB,OAAO,mBAAmB,CAAA;IAC5B,CAAC;IAED,MAAM,WAAW,GAAG,cAAc,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,UAAU,CAAA;IAE5E,mBAAmB,GAAG,CAAC,KAAK,IAAI,EAAE;QAChC,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,uBAAuB,CAAC,WAAW,EAAE,CAAC,CAAC,CAAA;YAC3D,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,OAAO,KAAK,CAAA;YACd,CAAC;YAED,iBAAiB,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;YAC9B,MAAM,EAAC,WAAW,EAAE,aAAa,EAAC,GAAG,eAAe,CAAC,QAAQ,EAAE,CAAA;YAC/D,MAAM,WAAW,GAAG,qBAAqB,CAAC,aAAa,EAAE,WAAW,CAAC,CAAA;YACrE,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,OAAO,CAAC,IAAI,CAAC,+EAA+E,CAAC,CAAA;gBAC7F,OAAO,KAAK,CAAA;YACd,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,oEAAoE,CAAC,CAAA;YACjF,MAAM,YAAY,CAAC,cAAc,CAAC,WAAW,CAAC,CAAA;YAC9C,OAAO,IAAI,CAAA;QACb,CAAC;gBAAS,CAAC;YACT,mBAAmB,GAAG,IAAI,CAAA;QAC5B,CAAC;IACH,CAAC,CAAC,EAAE,CAAA;IAEJ,OAAO,mBAAmB,CAAA;AAC5B,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,mCAAmC,CAAC,YAAgC;IACxF,IAAI,OAAO,YAAY,KAAK,QAAQ,IAAI,YAAY,IAAI,CAAC,EAAE,CAAC;QAC1D,OAAO,KAAK,CAAA;IACd,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,uBAAuB,CAAC,YAAY,EAAE,CAAC,CAAC,CAAA;IAC5D,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,KAAK,CAAA;IACd,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC","sourcesContent":["/**\n * Push phone time to glasses only when clock skew is detected (gallery sync, OTA, etc.).\n */\n\nimport BluetoothSdk from \"@mentra/bluetooth-sdk/internal\"\nimport {BgTimer} from \"../utils/timers\"\nimport {useGlassesStore} from \"../stores/glasses\"\n\nimport {resolveOtaManifestUrl} from \"./otaManifestUrl\"\nimport {detectClockSkew} from \"./gallerySyncClock\"\n\nexport const CLOCK_SETTLE_MS = 500\n\nconst OTA_CLOCK_FIX_COOLDOWN_MS = 30_000\nlet lastOtaClockFixAt = 0\n// Coalesce concurrent OTA clock-fix attempts. Glasses can emit two clock_skew events\n// back-to-back during BLE handshake; without this guard both would await the BLE fix\n// + retry pair before either updated `lastOtaClockFixAt`.\nlet inflightOtaClockFix: Promise<boolean> | null = null\n\n/** @internal test-only */\nexport function resetOtaClockFixCooldownForTests(): void {\n lastOtaClockFixAt = 0\n inflightOtaClockFix = null\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => BgTimer.setTimeout(resolve, ms))\n}\n\n/**\n * Set glasses system clock from phone when skew is detected.\n */\nexport async function fixGlassesClockIfSkewed(glassesServerTime: number, lastSyncTime = 0): Promise<boolean> {\n const {skewed, reason} = detectClockSkew(Date.now(), glassesServerTime, lastSyncTime)\n if (!skewed) {\n return false\n }\n console.log(`[GlassesClockSync] ⏰ Clock skew detected (${reason}) — setting glasses time from phone`)\n await BluetoothSdk.setSystemTime(Date.now())\n await delay(CLOCK_SETTLE_MS)\n return true\n}\n\n/**\n * After glasses report OTA failure due to clock skew, fix time and restart\n * through the normal phone-driven ota_start path.\n */\nexport async function handleOtaClockSkewFromGlasses(\n errorCode: string | undefined,\n glassesTimeMs?: number,\n): Promise<boolean> {\n if (Date.now() - lastOtaClockFixAt < OTA_CLOCK_FIX_COOLDOWN_MS) {\n return false\n }\n\n const hasGlassesTime = typeof glassesTimeMs === \"number\" && Number.isFinite(glassesTimeMs) && glassesTimeMs > 0\n\n if (errorCode === \"ssl_error\") {\n if (!hasGlassesTime) {\n return false\n }\n const {skewed} = detectClockSkew(Date.now(), glassesTimeMs, 0)\n if (!skewed) {\n return false\n }\n console.log(\"[GlassesClockSync] ⏰ OTA ssl_error with wall-clock drift — treating as clock skew\")\n } else if (errorCode !== \"clock_skew\") {\n return false\n }\n\n // Coalesce concurrent attempts: if a fix is already in flight, await its result instead\n // of racing it through the cooldown guard.\n if (inflightOtaClockFix) {\n return inflightOtaClockFix\n }\n\n const glassesTime = hasGlassesTime ? glassesTimeMs : Date.now() - 86_400_000\n\n inflightOtaClockFix = (async () => {\n try {\n const fixed = await fixGlassesClockIfSkewed(glassesTime, 0)\n if (!fixed) {\n return false\n }\n\n lastOtaClockFixAt = Date.now()\n const {buildNumber, otaVersionUrl} = useGlassesStore.getState()\n const manifestUrl = resolveOtaManifestUrl(otaVersionUrl, buildNumber)\n if (!manifestUrl) {\n console.warn(\"[GlassesClockSync] OTA restart skipped because this build has no manifest pin\")\n return false\n }\n console.log(\"[GlassesClockSync] ⏰ Restarting OTA with ota_start after clock fix\")\n await BluetoothSdk.startOtaUpdate(manifestUrl)\n return true\n } finally {\n inflightOtaClockFix = null\n }\n })()\n\n return inflightOtaClockFix\n}\n\n/**\n * Proactive fix when version_info includes glasses system_time_ms before OTA manifest checks.\n */\nexport async function maybeFixGlassesClockFromVersionInfo(systemTimeMs: number | undefined): Promise<boolean> {\n if (typeof systemTimeMs !== \"number\" || systemTimeMs <= 0) {\n return false\n }\n\n const fixed = await fixGlassesClockIfSkewed(systemTimeMs, 0)\n if (!fixed) {\n return false\n }\n\n return true\n}\n"]}