@mentra/engine 0.1.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (842) hide show
  1. package/README.md +79 -0
  2. package/build/devtools.d.ts +11 -0
  3. package/build/devtools.d.ts.map +1 -0
  4. package/build/devtools.js +13 -0
  5. package/build/devtools.js.map +1 -0
  6. package/build/engine.d.ts +261 -0
  7. package/build/engine.d.ts.map +1 -0
  8. package/build/engine.js +170 -0
  9. package/build/engine.js.map +1 -0
  10. package/build/facades/dev.d.ts +58 -0
  11. package/build/facades/dev.d.ts.map +1 -0
  12. package/build/facades/dev.js +189 -0
  13. package/build/facades/dev.js.map +1 -0
  14. package/build/facades/display.d.ts +21 -0
  15. package/build/facades/display.d.ts.map +1 -0
  16. package/build/facades/display.js +17 -0
  17. package/build/facades/display.js.map +1 -0
  18. package/build/facades/displayMirror.d.ts +15 -0
  19. package/build/facades/displayMirror.d.ts.map +1 -0
  20. package/build/facades/displayMirror.js +29 -0
  21. package/build/facades/displayMirror.js.map +1 -0
  22. package/build/facades/gallery.d.ts +62 -0
  23. package/build/facades/gallery.d.ts.map +1 -0
  24. package/build/facades/gallery.js +61 -0
  25. package/build/facades/gallery.js.map +1 -0
  26. package/build/facades/glasses.d.ts +141 -0
  27. package/build/facades/glasses.d.ts.map +1 -0
  28. package/build/facades/glasses.js +199 -0
  29. package/build/facades/glasses.js.map +1 -0
  30. package/build/facades/glassesSettings.d.ts +13 -0
  31. package/build/facades/glassesSettings.d.ts.map +1 -0
  32. package/build/facades/glassesSettings.js +54 -0
  33. package/build/facades/glassesSettings.js.map +1 -0
  34. package/build/facades/glassesWifi.d.ts +25 -0
  35. package/build/facades/glassesWifi.d.ts.map +1 -0
  36. package/build/facades/glassesWifi.js +57 -0
  37. package/build/facades/glassesWifi.js.map +1 -0
  38. package/build/facades/miniapps.d.ts +43 -0
  39. package/build/facades/miniapps.d.ts.map +1 -0
  40. package/build/facades/miniapps.js +49 -0
  41. package/build/facades/miniapps.js.map +1 -0
  42. package/build/facades/notifications.d.ts +13 -0
  43. package/build/facades/notifications.d.ts.map +1 -0
  44. package/build/facades/notifications.js +12 -0
  45. package/build/facades/notifications.js.map +1 -0
  46. package/build/facades/ota.d.ts +103 -0
  47. package/build/facades/ota.d.ts.map +1 -0
  48. package/build/facades/ota.js +156 -0
  49. package/build/facades/ota.js.map +1 -0
  50. package/build/facades/pairing.d.ts +114 -0
  51. package/build/facades/pairing.d.ts.map +1 -0
  52. package/build/facades/pairing.js +275 -0
  53. package/build/facades/pairing.js.map +1 -0
  54. package/build/facades/permissions.d.ts +33 -0
  55. package/build/facades/permissions.d.ts.map +1 -0
  56. package/build/facades/permissions.js +155 -0
  57. package/build/facades/permissions.js.map +1 -0
  58. package/build/facades/phoneNotifications.d.ts +21 -0
  59. package/build/facades/phoneNotifications.d.ts.map +1 -0
  60. package/build/facades/phoneNotifications.js +51 -0
  61. package/build/facades/phoneNotifications.js.map +1 -0
  62. package/build/facades/reports.d.ts +36 -0
  63. package/build/facades/reports.d.ts.map +1 -0
  64. package/build/facades/reports.js +134 -0
  65. package/build/facades/reports.js.map +1 -0
  66. package/build/facades/session.d.ts +21 -0
  67. package/build/facades/session.d.ts.map +1 -0
  68. package/build/facades/session.js +52 -0
  69. package/build/facades/session.js.map +1 -0
  70. package/build/facades/settings.d.ts +37 -0
  71. package/build/facades/settings.d.ts.map +1 -0
  72. package/build/facades/settings.js +55 -0
  73. package/build/facades/settings.js.map +1 -0
  74. package/build/facades/speech.d.ts +30 -0
  75. package/build/facades/speech.d.ts.map +1 -0
  76. package/build/facades/speech.js +37 -0
  77. package/build/facades/speech.js.map +1 -0
  78. package/build/index.d.ts +43 -0
  79. package/build/index.d.ts.map +1 -0
  80. package/build/index.js +45 -0
  81. package/build/index.js.map +1 -0
  82. package/build/internal.d.ts +67 -0
  83. package/build/internal.d.ts.map +1 -0
  84. package/build/internal.js +90 -0
  85. package/build/internal.js.map +1 -0
  86. package/build/runtime/bootstrap.d.ts +83 -0
  87. package/build/runtime/bootstrap.d.ts.map +1 -0
  88. package/build/runtime/bootstrap.js +56 -0
  89. package/build/runtime/bootstrap.js.map +1 -0
  90. package/build/runtime/config.d.ts +146 -0
  91. package/build/runtime/config.d.ts.map +1 -0
  92. package/build/runtime/config.js +18 -0
  93. package/build/runtime/config.js.map +1 -0
  94. package/build/runtime/streamConfig.d.ts +4 -0
  95. package/build/runtime/streamConfig.d.ts.map +1 -0
  96. package/build/runtime/streamConfig.js +38 -0
  97. package/build/runtime/streamConfig.js.map +1 -0
  98. package/build/services/AppRegistry.d.ts +269 -0
  99. package/build/services/AppRegistry.d.ts.map +1 -0
  100. package/build/services/AppRegistry.js +1079 -0
  101. package/build/services/AppRegistry.js.map +1 -0
  102. package/build/services/AudioCloudUplink.d.ts +9 -0
  103. package/build/services/AudioCloudUplink.d.ts.map +1 -0
  104. package/build/services/AudioCloudUplink.js +48 -0
  105. package/build/services/AudioCloudUplink.js.map +1 -0
  106. package/build/services/AudioPlaybackService.d.ts +175 -0
  107. package/build/services/AudioPlaybackService.d.ts.map +1 -0
  108. package/build/services/AudioPlaybackService.js +705 -0
  109. package/build/services/AudioPlaybackService.js.map +1 -0
  110. package/build/services/AutomaticReportResult.d.ts +20 -0
  111. package/build/services/AutomaticReportResult.d.ts.map +1 -0
  112. package/build/services/AutomaticReportResult.js +29 -0
  113. package/build/services/AutomaticReportResult.js.map +1 -0
  114. package/build/services/BlobStore.d.ts +91 -0
  115. package/build/services/BlobStore.d.ts.map +1 -0
  116. package/build/services/BlobStore.js +668 -0
  117. package/build/services/BlobStore.js.map +1 -0
  118. package/build/services/CaptionsTesterReportService.d.ts +4 -0
  119. package/build/services/CaptionsTesterReportService.d.ts.map +1 -0
  120. package/build/services/CaptionsTesterReportService.js +80 -0
  121. package/build/services/CaptionsTesterReportService.js.map +1 -0
  122. package/build/services/CloudAudioSubscriptionSync.d.ts +24 -0
  123. package/build/services/CloudAudioSubscriptionSync.d.ts.map +1 -0
  124. package/build/services/CloudAudioSubscriptionSync.js +39 -0
  125. package/build/services/CloudAudioSubscriptionSync.js.map +1 -0
  126. package/build/services/CloudClientService.d.ts +143 -0
  127. package/build/services/CloudClientService.d.ts.map +1 -0
  128. package/build/services/CloudClientService.js +644 -0
  129. package/build/services/CloudClientService.js.map +1 -0
  130. package/build/services/CloudTranscriptE2EMetrics.d.ts +4 -0
  131. package/build/services/CloudTranscriptE2EMetrics.d.ts.map +1 -0
  132. package/build/services/CloudTranscriptE2EMetrics.js +24 -0
  133. package/build/services/CloudTranscriptE2EMetrics.js.map +1 -0
  134. package/build/services/ConnectionCoordinator.d.ts +51 -0
  135. package/build/services/ConnectionCoordinator.d.ts.map +1 -0
  136. package/build/services/ConnectionCoordinator.js +34 -0
  137. package/build/services/ConnectionCoordinator.js.map +1 -0
  138. package/build/services/DevServerBridge.d.ts +63 -0
  139. package/build/services/DevServerBridge.d.ts.map +1 -0
  140. package/build/services/DevServerBridge.js +294 -0
  141. package/build/services/DevServerBridge.js.map +1 -0
  142. package/build/services/DeviceEventRouter.d.ts +3 -0
  143. package/build/services/DeviceEventRouter.d.ts.map +1 -0
  144. package/build/services/DeviceEventRouter.js +190 -0
  145. package/build/services/DeviceEventRouter.js.map +1 -0
  146. package/build/services/DeviceStoreHydration.d.ts +38 -0
  147. package/build/services/DeviceStoreHydration.d.ts.map +1 -0
  148. package/build/services/DeviceStoreHydration.js +124 -0
  149. package/build/services/DeviceStoreHydration.js.map +1 -0
  150. package/build/services/DisplayProcessor.d.ts +223 -0
  151. package/build/services/DisplayProcessor.d.ts.map +1 -0
  152. package/build/services/DisplayProcessor.js +552 -0
  153. package/build/services/DisplayProcessor.js.map +1 -0
  154. package/build/services/GlassesReadiness.d.ts +41 -0
  155. package/build/services/GlassesReadiness.d.ts.map +1 -0
  156. package/build/services/GlassesReadiness.js +82 -0
  157. package/build/services/GlassesReadiness.js.map +1 -0
  158. package/build/services/GlassesSettingsSync.d.ts +33 -0
  159. package/build/services/GlassesSettingsSync.d.ts.map +1 -0
  160. package/build/services/GlassesSettingsSync.js +121 -0
  161. package/build/services/GlassesSettingsSync.js.map +1 -0
  162. package/build/services/GlassesStatusProjection.d.ts +3 -0
  163. package/build/services/GlassesStatusProjection.d.ts.map +1 -0
  164. package/build/services/GlassesStatusProjection.js +65 -0
  165. package/build/services/GlassesStatusProjection.js.map +1 -0
  166. package/build/services/HeadingService.d.ts +27 -0
  167. package/build/services/HeadingService.d.ts.map +1 -0
  168. package/build/services/HeadingService.js +96 -0
  169. package/build/services/HeadingService.js.map +1 -0
  170. package/build/services/LocalDisplayManager.d.ts +139 -0
  171. package/build/services/LocalDisplayManager.d.ts.map +1 -0
  172. package/build/services/LocalDisplayManager.js +579 -0
  173. package/build/services/LocalDisplayManager.js.map +1 -0
  174. package/build/services/LocalMiniappRuntime.d.ts +439 -0
  175. package/build/services/LocalMiniappRuntime.d.ts.map +1 -0
  176. package/build/services/LocalMiniappRuntime.js +3695 -0
  177. package/build/services/LocalMiniappRuntime.js.map +1 -0
  178. package/build/services/LocalSttFallbackCoordinator.d.ts +57 -0
  179. package/build/services/LocalSttFallbackCoordinator.d.ts.map +1 -0
  180. package/build/services/LocalSttFallbackCoordinator.js +137 -0
  181. package/build/services/LocalSttFallbackCoordinator.js.map +1 -0
  182. package/build/services/MentraJSCrashController.d.ts +97 -0
  183. package/build/services/MentraJSCrashController.d.ts.map +1 -0
  184. package/build/services/MentraJSCrashController.js +138 -0
  185. package/build/services/MentraJSCrashController.js.map +1 -0
  186. package/build/services/MentraJSCrashloopReportService.d.ts +9 -0
  187. package/build/services/MentraJSCrashloopReportService.d.ts.map +1 -0
  188. package/build/services/MentraJSCrashloopReportService.js +54 -0
  189. package/build/services/MentraJSCrashloopReportService.js.map +1 -0
  190. package/build/services/MentraJSLogPipeline.d.ts +87 -0
  191. package/build/services/MentraJSLogPipeline.d.ts.map +1 -0
  192. package/build/services/MentraJSLogPipeline.js +137 -0
  193. package/build/services/MentraJSLogPipeline.js.map +1 -0
  194. package/build/services/MentraJSRouter.d.ts +203 -0
  195. package/build/services/MentraJSRouter.d.ts.map +1 -0
  196. package/build/services/MentraJSRouter.js +480 -0
  197. package/build/services/MentraJSRouter.js.map +1 -0
  198. package/build/services/MentraUIRouter.d.ts +137 -0
  199. package/build/services/MentraUIRouter.d.ts.map +1 -0
  200. package/build/services/MentraUIRouter.js +210 -0
  201. package/build/services/MentraUIRouter.js.map +1 -0
  202. package/build/services/MicStateCoordinator.d.ts +37 -0
  203. package/build/services/MicStateCoordinator.d.ts.map +1 -0
  204. package/build/services/MicStateCoordinator.js +81 -0
  205. package/build/services/MicStateCoordinator.js.map +1 -0
  206. package/build/services/MiniappEngine.d.ts +31 -0
  207. package/build/services/MiniappEngine.d.ts.map +1 -0
  208. package/build/services/MiniappEngine.js +119 -0
  209. package/build/services/MiniappEngine.js.map +1 -0
  210. package/build/services/MiniappLauncher.d.ts +129 -0
  211. package/build/services/MiniappLauncher.d.ts.map +1 -0
  212. package/build/services/MiniappLauncher.js +291 -0
  213. package/build/services/MiniappLauncher.js.map +1 -0
  214. package/build/services/MiniappRunningRegistry.d.ts +24 -0
  215. package/build/services/MiniappRunningRegistry.d.ts.map +1 -0
  216. package/build/services/MiniappRunningRegistry.js +52 -0
  217. package/build/services/MiniappRunningRegistry.js.map +1 -0
  218. package/build/services/NativeHttpResponse.d.ts +3 -0
  219. package/build/services/NativeHttpResponse.d.ts.map +1 -0
  220. package/build/services/NativeHttpResponse.js +6 -0
  221. package/build/services/NativeHttpResponse.js.map +1 -0
  222. package/build/services/NavigationHandlers.d.ts +90 -0
  223. package/build/services/NavigationHandlers.d.ts.map +1 -0
  224. package/build/services/NavigationHandlers.js +379 -0
  225. package/build/services/NavigationHandlers.js.map +1 -0
  226. package/build/services/NavigationService.d.ts +254 -0
  227. package/build/services/NavigationService.d.ts.map +1 -0
  228. package/build/services/NavigationService.js +553 -0
  229. package/build/services/NavigationService.js.map +1 -0
  230. package/build/services/NotificationsEmitter.d.ts +28 -0
  231. package/build/services/NotificationsEmitter.d.ts.map +1 -0
  232. package/build/services/NotificationsEmitter.js +22 -0
  233. package/build/services/NotificationsEmitter.js.map +1 -0
  234. package/build/services/OfflineSpeechModelService.d.ts +33 -0
  235. package/build/services/OfflineSpeechModelService.d.ts.map +1 -0
  236. package/build/services/OfflineSpeechModelService.js +136 -0
  237. package/build/services/OfflineSpeechModelService.js.map +1 -0
  238. package/build/services/OtaInstallCoordinator.d.ts +195 -0
  239. package/build/services/OtaInstallCoordinator.d.ts.map +1 -0
  240. package/build/services/OtaInstallCoordinator.js +1047 -0
  241. package/build/services/OtaInstallCoordinator.js.map +1 -0
  242. package/build/services/OtaService.d.ts +3 -0
  243. package/build/services/OtaService.d.ts.map +1 -0
  244. package/build/services/OtaService.js +65 -0
  245. package/build/services/OtaService.js.map +1 -0
  246. package/build/services/OtaUpdateCheckService.d.ts +65 -0
  247. package/build/services/OtaUpdateCheckService.d.ts.map +1 -0
  248. package/build/services/OtaUpdateCheckService.js +260 -0
  249. package/build/services/OtaUpdateCheckService.js.map +1 -0
  250. package/build/services/PairingIdentity.d.ts +80 -0
  251. package/build/services/PairingIdentity.d.ts.map +1 -0
  252. package/build/services/PairingIdentity.js +145 -0
  253. package/build/services/PairingIdentity.js.map +1 -0
  254. package/build/services/PhoneCalendarHelpers.d.ts +32 -0
  255. package/build/services/PhoneCalendarHelpers.d.ts.map +1 -0
  256. package/build/services/PhoneCalendarHelpers.js +157 -0
  257. package/build/services/PhoneCalendarHelpers.js.map +1 -0
  258. package/build/services/PhoneCalendarService.d.ts +5 -0
  259. package/build/services/PhoneCalendarService.d.ts.map +1 -0
  260. package/build/services/PhoneCalendarService.js +24 -0
  261. package/build/services/PhoneCalendarService.js.map +1 -0
  262. package/build/services/PhoneCameraFovCoordinator.d.ts +28 -0
  263. package/build/services/PhoneCameraFovCoordinator.d.ts.map +1 -0
  264. package/build/services/PhoneCameraFovCoordinator.js +180 -0
  265. package/build/services/PhoneCameraFovCoordinator.js.map +1 -0
  266. package/build/services/PhoneLocationService.d.ts +36 -0
  267. package/build/services/PhoneLocationService.d.ts.map +1 -0
  268. package/build/services/PhoneLocationService.js +115 -0
  269. package/build/services/PhoneLocationService.js.map +1 -0
  270. package/build/services/PhoneNotificationsSync.d.ts +3 -0
  271. package/build/services/PhoneNotificationsSync.d.ts.map +1 -0
  272. package/build/services/PhoneNotificationsSync.js +35 -0
  273. package/build/services/PhoneNotificationsSync.js.map +1 -0
  274. package/build/services/PhonePhotoCoordinator.d.ts +103 -0
  275. package/build/services/PhonePhotoCoordinator.d.ts.map +1 -0
  276. package/build/services/PhonePhotoCoordinator.js +326 -0
  277. package/build/services/PhonePhotoCoordinator.js.map +1 -0
  278. package/build/services/PhoneStreamCoordinator.d.ts +168 -0
  279. package/build/services/PhoneStreamCoordinator.d.ts.map +1 -0
  280. package/build/services/PhoneStreamCoordinator.js +632 -0
  281. package/build/services/PhoneStreamCoordinator.js.map +1 -0
  282. package/build/services/PhoneVideoCoordinator.d.ts +50 -0
  283. package/build/services/PhoneVideoCoordinator.d.ts.map +1 -0
  284. package/build/services/PhoneVideoCoordinator.js +147 -0
  285. package/build/services/PhoneVideoCoordinator.js.map +1 -0
  286. package/build/services/STTModelManager.d.ts +65 -0
  287. package/build/services/STTModelManager.d.ts.map +1 -0
  288. package/build/services/STTModelManager.js +292 -0
  289. package/build/services/STTModelManager.js.map +1 -0
  290. package/build/services/SceneRenderer.d.ts +90 -0
  291. package/build/services/SceneRenderer.d.ts.map +1 -0
  292. package/build/services/SceneRenderer.js +248 -0
  293. package/build/services/SceneRenderer.js.map +1 -0
  294. package/build/services/SentenceTtsPipeline.d.ts +31 -0
  295. package/build/services/SentenceTtsPipeline.d.ts.map +1 -0
  296. package/build/services/SentenceTtsPipeline.js +67 -0
  297. package/build/services/SentenceTtsPipeline.js.map +1 -0
  298. package/build/services/StreamLifecycleController.d.ts +67 -0
  299. package/build/services/StreamLifecycleController.d.ts.map +1 -0
  300. package/build/services/StreamLifecycleController.js +152 -0
  301. package/build/services/StreamLifecycleController.js.map +1 -0
  302. package/build/services/TTSModelManager.d.ts +90 -0
  303. package/build/services/TTSModelManager.d.ts.map +1 -0
  304. package/build/services/TTSModelManager.js +366 -0
  305. package/build/services/TTSModelManager.js.map +1 -0
  306. package/build/services/TranscriptionRouting.d.ts +12 -0
  307. package/build/services/TranscriptionRouting.d.ts.map +1 -0
  308. package/build/services/TranscriptionRouting.js +24 -0
  309. package/build/services/TranscriptionRouting.js.map +1 -0
  310. package/build/services/WebviewBridge.d.ts +22 -0
  311. package/build/services/WebviewBridge.d.ts.map +1 -0
  312. package/build/services/WebviewBridge.js +47 -0
  313. package/build/services/WebviewBridge.js.map +1 -0
  314. package/build/services/asg/GalleryMediaIntegrityReportService.d.ts +17 -0
  315. package/build/services/asg/GalleryMediaIntegrityReportService.d.ts.map +1 -0
  316. package/build/services/asg/GalleryMediaIntegrityReportService.js +43 -0
  317. package/build/services/asg/GalleryMediaIntegrityReportService.js.map +1 -0
  318. package/build/services/asg/asgCameraApi.d.ts +205 -0
  319. package/build/services/asg/asgCameraApi.d.ts.map +1 -0
  320. package/build/services/asg/asgCameraApi.js +1410 -0
  321. package/build/services/asg/asgCameraApi.js.map +1 -0
  322. package/build/services/asg/cameraRollExportCoordinator.d.ts +60 -0
  323. package/build/services/asg/cameraRollExportCoordinator.d.ts.map +1 -0
  324. package/build/services/asg/cameraRollExportCoordinator.js +618 -0
  325. package/build/services/asg/cameraRollExportCoordinator.js.map +1 -0
  326. package/build/services/asg/cameraRollExportLedger.d.ts +44 -0
  327. package/build/services/asg/cameraRollExportLedger.d.ts.map +1 -0
  328. package/build/services/asg/cameraRollExportLedger.js +78 -0
  329. package/build/services/asg/cameraRollExportLedger.js.map +1 -0
  330. package/build/services/asg/galleryMediaValidation.d.ts +29 -0
  331. package/build/services/asg/galleryMediaValidation.d.ts.map +1 -0
  332. package/build/services/asg/galleryMediaValidation.js +149 -0
  333. package/build/services/asg/galleryMediaValidation.js.map +1 -0
  334. package/build/services/asg/galleryNotices.d.ts +33 -0
  335. package/build/services/asg/galleryNotices.d.ts.map +1 -0
  336. package/build/services/asg/galleryNotices.js +29 -0
  337. package/build/services/asg/galleryNotices.js.map +1 -0
  338. package/build/services/asg/galleryPathMigration.d.ts +3 -0
  339. package/build/services/asg/galleryPathMigration.d.ts.map +1 -0
  340. package/build/services/asg/galleryPathMigration.js +26 -0
  341. package/build/services/asg/galleryPathMigration.js.map +1 -0
  342. package/build/services/asg/gallerySettingsService.d.ts +20 -0
  343. package/build/services/asg/gallerySettingsService.d.ts.map +1 -0
  344. package/build/services/asg/gallerySettingsService.js +53 -0
  345. package/build/services/asg/gallerySettingsService.js.map +1 -0
  346. package/build/services/asg/gallerySyncNotifications.d.ts +56 -0
  347. package/build/services/asg/gallerySyncNotifications.d.ts.map +1 -0
  348. package/build/services/asg/gallerySyncNotifications.js +198 -0
  349. package/build/services/asg/gallerySyncNotifications.js.map +1 -0
  350. package/build/services/asg/gallerySyncService.d.ts +137 -0
  351. package/build/services/asg/gallerySyncService.d.ts.map +1 -0
  352. package/build/services/asg/gallerySyncService.js +1933 -0
  353. package/build/services/asg/gallerySyncService.js.map +1 -0
  354. package/build/services/asg/galleryTransferLedger.d.ts +58 -0
  355. package/build/services/asg/galleryTransferLedger.d.ts.map +1 -0
  356. package/build/services/asg/galleryTransferLedger.js +225 -0
  357. package/build/services/asg/galleryTransferLedger.js.map +1 -0
  358. package/build/services/asg/localNetworkTransport.d.ts +15 -0
  359. package/build/services/asg/localNetworkTransport.d.ts.map +1 -0
  360. package/build/services/asg/localNetworkTransport.js +144 -0
  361. package/build/services/asg/localNetworkTransport.js.map +1 -0
  362. package/build/services/asg/localStorageService.d.ts +146 -0
  363. package/build/services/asg/localStorageService.d.ts.map +1 -0
  364. package/build/services/asg/localStorageService.js +572 -0
  365. package/build/services/asg/localStorageService.js.map +1 -0
  366. package/build/services/asg/mediaProcessingQueue.d.ts +70 -0
  367. package/build/services/asg/mediaProcessingQueue.d.ts.map +1 -0
  368. package/build/services/asg/mediaProcessingQueue.js +451 -0
  369. package/build/services/asg/mediaProcessingQueue.js.map +1 -0
  370. package/build/services/asgOtaVersionUrl.d.ts +2 -0
  371. package/build/services/asgOtaVersionUrl.d.ts.map +1 -0
  372. package/build/services/asgOtaVersionUrl.js +36 -0
  373. package/build/services/asgOtaVersionUrl.js.map +1 -0
  374. package/build/services/blobPaths.d.ts +31 -0
  375. package/build/services/blobPaths.d.ts.map +1 -0
  376. package/build/services/blobPaths.js +86 -0
  377. package/build/services/blobPaths.js.map +1 -0
  378. package/build/services/cloudStreamApi.d.ts +59 -0
  379. package/build/services/cloudStreamApi.d.ts.map +1 -0
  380. package/build/services/cloudStreamApi.js +81 -0
  381. package/build/services/cloudStreamApi.js.map +1 -0
  382. package/build/services/gallerySyncClock.d.ts +17 -0
  383. package/build/services/gallerySyncClock.d.ts.map +1 -0
  384. package/build/services/gallerySyncClock.js +21 -0
  385. package/build/services/gallerySyncClock.js.map +1 -0
  386. package/build/services/glassesClockSync.d.ts +20 -0
  387. package/build/services/glassesClockSync.d.ts.map +1 -0
  388. package/build/services/glassesClockSync.js +97 -0
  389. package/build/services/glassesClockSync.js.map +1 -0
  390. package/build/services/manifestVersionGate.d.ts +28 -0
  391. package/build/services/manifestVersionGate.d.ts.map +1 -0
  392. package/build/services/manifestVersionGate.js +41 -0
  393. package/build/services/manifestVersionGate.js.map +1 -0
  394. package/build/services/mentraUiShim.d.ts +38 -0
  395. package/build/services/mentraUiShim.d.ts.map +1 -0
  396. package/build/services/mentraUiShim.js +360 -0
  397. package/build/services/mentraUiShim.js.map +1 -0
  398. package/build/services/navigation/roadNameResolver.d.ts +67 -0
  399. package/build/services/navigation/roadNameResolver.d.ts.map +1 -0
  400. package/build/services/navigation/roadNameResolver.js +96 -0
  401. package/build/services/navigation/roadNameResolver.js.map +1 -0
  402. package/build/services/navigation/routesApiCodec.d.ts +38 -0
  403. package/build/services/navigation/routesApiCodec.d.ts.map +1 -0
  404. package/build/services/navigation/routesApiCodec.js +68 -0
  405. package/build/services/navigation/routesApiCodec.js.map +1 -0
  406. package/build/services/otaDisplayState.d.ts +42 -0
  407. package/build/services/otaDisplayState.d.ts.map +1 -0
  408. package/build/services/otaDisplayState.js +92 -0
  409. package/build/services/otaDisplayState.js.map +1 -0
  410. package/build/services/otaInstallPolicy.d.ts +81 -0
  411. package/build/services/otaInstallPolicy.d.ts.map +1 -0
  412. package/build/services/otaInstallPolicy.js +96 -0
  413. package/build/services/otaInstallPolicy.js.map +1 -0
  414. package/build/services/otaLegacyMapping.d.ts +22 -0
  415. package/build/services/otaLegacyMapping.d.ts.map +1 -0
  416. package/build/services/otaLegacyMapping.js +56 -0
  417. package/build/services/otaLegacyMapping.js.map +1 -0
  418. package/build/services/slimStreamStatus.d.ts +7 -0
  419. package/build/services/slimStreamStatus.d.ts.map +1 -0
  420. package/build/services/slimStreamStatus.js +26 -0
  421. package/build/services/slimStreamStatus.js.map +1 -0
  422. package/build/stores/apps.d.ts +104 -0
  423. package/build/stores/apps.d.ts.map +1 -0
  424. package/build/stores/apps.js +492 -0
  425. package/build/stores/apps.js.map +1 -0
  426. package/build/stores/bluetoothSettingKeys.d.ts +4 -0
  427. package/build/stores/bluetoothSettingKeys.d.ts.map +1 -0
  428. package/build/stores/bluetoothSettingKeys.js +31 -0
  429. package/build/stores/bluetoothSettingKeys.js.map +1 -0
  430. package/build/stores/cloudClientStatus.d.ts +9 -0
  431. package/build/stores/cloudClientStatus.d.ts.map +1 -0
  432. package/build/stores/cloudClientStatus.js +24 -0
  433. package/build/stores/cloudClientStatus.js.map +1 -0
  434. package/build/stores/connection.d.ts +22 -0
  435. package/build/stores/connection.d.ts.map +1 -0
  436. package/build/stores/connection.js +43 -0
  437. package/build/stores/connection.js.map +1 -0
  438. package/build/stores/core.d.ts +15 -0
  439. package/build/stores/core.d.ts.map +1 -0
  440. package/build/stores/core.js +21 -0
  441. package/build/stores/core.js.map +1 -0
  442. package/build/stores/display.d.ts +24 -0
  443. package/build/stores/display.d.ts.map +1 -0
  444. package/build/stores/display.js +96 -0
  445. package/build/stores/display.js.map +1 -0
  446. package/build/stores/gallerySync.d.ts +101 -0
  447. package/build/stores/gallerySync.d.ts.map +1 -0
  448. package/build/stores/gallerySync.js +217 -0
  449. package/build/stores/gallerySync.js.map +1 -0
  450. package/build/stores/glasses.d.ts +62 -0
  451. package/build/stores/glasses.d.ts.map +1 -0
  452. package/build/stores/glasses.js +203 -0
  453. package/build/stores/glasses.js.map +1 -0
  454. package/build/stores/settings.d.ts +37 -0
  455. package/build/stores/settings.d.ts.map +1 -0
  456. package/build/stores/settings.js +922 -0
  457. package/build/stores/settings.js.map +1 -0
  458. package/build/types/applet.d.ts +96 -0
  459. package/build/types/applet.d.ts.map +1 -0
  460. package/build/types/applet.js +5 -0
  461. package/build/types/applet.js.map +1 -0
  462. package/build/types/asg.d.ts +53 -0
  463. package/build/types/asg.d.ts.map +1 -0
  464. package/build/types/asg.js +2 -0
  465. package/build/types/asg.js.map +1 -0
  466. package/build/types/capabilities/even-realities-g1.d.ts +12 -0
  467. package/build/types/capabilities/even-realities-g1.d.ts.map +1 -0
  468. package/build/types/capabilities/even-realities-g1.js +55 -0
  469. package/build/types/capabilities/even-realities-g1.js.map +1 -0
  470. package/build/types/capabilities/even-realities-g2.d.ts +12 -0
  471. package/build/types/capabilities/even-realities-g2.d.ts.map +1 -0
  472. package/build/types/capabilities/even-realities-g2.js +75 -0
  473. package/build/types/capabilities/even-realities-g2.js.map +1 -0
  474. package/build/types/capabilities/mentra-display.d.ts +12 -0
  475. package/build/types/capabilities/mentra-display.d.ts.map +1 -0
  476. package/build/types/capabilities/mentra-display.js +63 -0
  477. package/build/types/capabilities/mentra-display.js.map +1 -0
  478. package/build/types/capabilities/mentra-live.d.ts +12 -0
  479. package/build/types/capabilities/mentra-live.d.ts.map +1 -0
  480. package/build/types/capabilities/mentra-live.js +92 -0
  481. package/build/types/capabilities/mentra-live.js.map +1 -0
  482. package/build/types/capabilities/none.d.ts +13 -0
  483. package/build/types/capabilities/none.d.ts.map +1 -0
  484. package/build/types/capabilities/none.js +65 -0
  485. package/build/types/capabilities/none.js.map +1 -0
  486. package/build/types/capabilities/simulated-glasses.d.ts +13 -0
  487. package/build/types/capabilities/simulated-glasses.d.ts.map +1 -0
  488. package/build/types/capabilities/simulated-glasses.js +65 -0
  489. package/build/types/capabilities/simulated-glasses.js.map +1 -0
  490. package/build/types/capabilities/vuzix-z100.d.ts +12 -0
  491. package/build/types/capabilities/vuzix-z100.d.ts.map +1 -0
  492. package/build/types/capabilities/vuzix-z100.js +52 -0
  493. package/build/types/capabilities/vuzix-z100.js.map +1 -0
  494. package/build/types/enums.d.ts +40 -0
  495. package/build/types/enums.d.ts.map +1 -0
  496. package/build/types/enums.js +44 -0
  497. package/build/types/enums.js.map +1 -0
  498. package/build/types/hardware.d.ts +160 -0
  499. package/build/types/hardware.d.ts.map +1 -0
  500. package/build/types/hardware.js +36 -0
  501. package/build/types/hardware.js.map +1 -0
  502. package/build/types/index.d.ts +11 -0
  503. package/build/types/index.d.ts.map +1 -0
  504. package/build/types/index.js +10 -0
  505. package/build/types/index.js.map +1 -0
  506. package/build/utils/GlobalEventEmitter.d.ts +10 -0
  507. package/build/utils/GlobalEventEmitter.d.ts.map +1 -0
  508. package/build/utils/GlobalEventEmitter.js +13 -0
  509. package/build/utils/GlobalEventEmitter.js.map +1 -0
  510. package/build/utils/cloudClient/RnUdpAdapter.d.ts +3 -0
  511. package/build/utils/cloudClient/RnUdpAdapter.d.ts.map +1 -0
  512. package/build/utils/cloudClient/RnUdpAdapter.js +41 -0
  513. package/build/utils/cloudClient/RnUdpAdapter.js.map +1 -0
  514. package/build/utils/cloudClient/cloudSecureStore.d.ts +3 -0
  515. package/build/utils/cloudClient/cloudSecureStore.d.ts.map +1 -0
  516. package/build/utils/cloudClient/cloudSecureStore.js +26 -0
  517. package/build/utils/cloudClient/cloudSecureStore.js.map +1 -0
  518. package/build/utils/debouncedPatch.d.ts +6 -0
  519. package/build/utils/debouncedPatch.d.ts.map +1 -0
  520. package/build/utils/debouncedPatch.js +25 -0
  521. package/build/utils/debouncedPatch.js.map +1 -0
  522. package/build/utils/devLogging.d.ts +26 -0
  523. package/build/utils/devLogging.d.ts.map +1 -0
  524. package/build/utils/devLogging.js +116 -0
  525. package/build/utils/devLogging.js.map +1 -0
  526. package/build/utils/devMiniappLaunch.d.ts +44 -0
  527. package/build/utils/devMiniappLaunch.d.ts.map +1 -0
  528. package/build/utils/devMiniappLaunch.js +52 -0
  529. package/build/utils/devMiniappLaunch.js.map +1 -0
  530. package/build/utils/diagnosticContext.d.ts +3 -0
  531. package/build/utils/diagnosticContext.d.ts.map +1 -0
  532. package/build/utils/diagnosticContext.js +119 -0
  533. package/build/utils/diagnosticContext.js.map +1 -0
  534. package/build/utils/display/composer/ColumnComposer.d.ts +131 -0
  535. package/build/utils/display/composer/ColumnComposer.d.ts.map +1 -0
  536. package/build/utils/display/composer/ColumnComposer.js +204 -0
  537. package/build/utils/display/composer/ColumnComposer.js.map +1 -0
  538. package/build/utils/display/composer/index.d.ts +9 -0
  539. package/build/utils/display/composer/index.d.ts.map +1 -0
  540. package/build/utils/display/composer/index.js +9 -0
  541. package/build/utils/display/composer/index.js.map +1 -0
  542. package/build/utils/display/helpers/DisplayHelpers.d.ts +165 -0
  543. package/build/utils/display/helpers/DisplayHelpers.d.ts.map +1 -0
  544. package/build/utils/display/helpers/DisplayHelpers.js +312 -0
  545. package/build/utils/display/helpers/DisplayHelpers.js.map +1 -0
  546. package/build/utils/display/helpers/ScrollView.d.ts +183 -0
  547. package/build/utils/display/helpers/ScrollView.d.ts.map +1 -0
  548. package/build/utils/display/helpers/ScrollView.js +257 -0
  549. package/build/utils/display/helpers/ScrollView.js.map +1 -0
  550. package/build/utils/display/helpers/index.d.ts +11 -0
  551. package/build/utils/display/helpers/index.d.ts.map +1 -0
  552. package/build/utils/display/helpers/index.js +10 -0
  553. package/build/utils/display/helpers/index.js.map +1 -0
  554. package/build/utils/display/index.d.ts +174 -0
  555. package/build/utils/display/index.d.ts.map +1 -0
  556. package/build/utils/display/index.js +185 -0
  557. package/build/utils/display/index.js.map +1 -0
  558. package/build/utils/display/measurer/TextMeasurer.d.ts +160 -0
  559. package/build/utils/display/measurer/TextMeasurer.d.ts.map +1 -0
  560. package/build/utils/display/measurer/TextMeasurer.js +273 -0
  561. package/build/utils/display/measurer/TextMeasurer.js.map +1 -0
  562. package/build/utils/display/measurer/index.d.ts +10 -0
  563. package/build/utils/display/measurer/index.d.ts.map +1 -0
  564. package/build/utils/display/measurer/index.js +11 -0
  565. package/build/utils/display/measurer/index.js.map +1 -0
  566. package/build/utils/display/measurer/script-detection.d.ts +53 -0
  567. package/build/utils/display/measurer/script-detection.d.ts.map +1 -0
  568. package/build/utils/display/measurer/script-detection.js +184 -0
  569. package/build/utils/display/measurer/script-detection.js.map +1 -0
  570. package/build/utils/display/profiles/g1.d.ts +33 -0
  571. package/build/utils/display/profiles/g1.d.ts.map +1 -0
  572. package/build/utils/display/profiles/g1.js +213 -0
  573. package/build/utils/display/profiles/g1.js.map +1 -0
  574. package/build/utils/display/profiles/g2.d.ts +13 -0
  575. package/build/utils/display/profiles/g2.d.ts.map +1 -0
  576. package/build/utils/display/profiles/g2.js +34 -0
  577. package/build/utils/display/profiles/g2.js.map +1 -0
  578. package/build/utils/display/profiles/index.d.ts +12 -0
  579. package/build/utils/display/profiles/index.d.ts.map +1 -0
  580. package/build/utils/display/profiles/index.js +15 -0
  581. package/build/utils/display/profiles/index.js.map +1 -0
  582. package/build/utils/display/profiles/nex.d.ts +10 -0
  583. package/build/utils/display/profiles/nex.d.ts.map +1 -0
  584. package/build/utils/display/profiles/nex.js +150 -0
  585. package/build/utils/display/profiles/nex.js.map +1 -0
  586. package/build/utils/display/profiles/types.d.ts +103 -0
  587. package/build/utils/display/profiles/types.d.ts.map +1 -0
  588. package/build/utils/display/profiles/types.js +2 -0
  589. package/build/utils/display/profiles/types.js.map +1 -0
  590. package/build/utils/display/profiles/z100.d.ts +33 -0
  591. package/build/utils/display/profiles/z100.d.ts.map +1 -0
  592. package/build/utils/display/profiles/z100.js +182 -0
  593. package/build/utils/display/profiles/z100.js.map +1 -0
  594. package/build/utils/display/scene/degrade.d.ts +25 -0
  595. package/build/utils/display/scene/degrade.d.ts.map +1 -0
  596. package/build/utils/display/scene/degrade.js +64 -0
  597. package/build/utils/display/scene/degrade.js.map +1 -0
  598. package/build/utils/display/scene/differ.d.ts +38 -0
  599. package/build/utils/display/scene/differ.d.ts.map +1 -0
  600. package/build/utils/display/scene/differ.js +107 -0
  601. package/build/utils/display/scene/differ.js.map +1 -0
  602. package/build/utils/display/scene/index.d.ts +10 -0
  603. package/build/utils/display/scene/index.d.ts.map +1 -0
  604. package/build/utils/display/scene/index.js +6 -0
  605. package/build/utils/display/scene/index.js.map +1 -0
  606. package/build/utils/display/scene/process.d.ts +35 -0
  607. package/build/utils/display/scene/process.d.ts.map +1 -0
  608. package/build/utils/display/scene/process.js +187 -0
  609. package/build/utils/display/scene/process.js.map +1 -0
  610. package/build/utils/display/scene/store.d.ts +48 -0
  611. package/build/utils/display/scene/store.d.ts.map +1 -0
  612. package/build/utils/display/scene/store.js +0 -0
  613. package/build/utils/display/scene/store.js.map +1 -0
  614. package/build/utils/display/scene/types.d.ts +120 -0
  615. package/build/utils/display/scene/types.d.ts.map +1 -0
  616. package/build/utils/display/scene/types.js +0 -0
  617. package/build/utils/display/scene/types.js.map +1 -0
  618. package/build/utils/display/wrapper/TextWrapper.d.ts +102 -0
  619. package/build/utils/display/wrapper/TextWrapper.d.ts.map +1 -0
  620. package/build/utils/display/wrapper/TextWrapper.js +471 -0
  621. package/build/utils/display/wrapper/TextWrapper.js.map +1 -0
  622. package/build/utils/display/wrapper/index.d.ts +12 -0
  623. package/build/utils/display/wrapper/index.d.ts.map +1 -0
  624. package/build/utils/display/wrapper/index.js +12 -0
  625. package/build/utils/display/wrapper/index.js.map +1 -0
  626. package/build/utils/display/wrapper/types.d.ts +75 -0
  627. package/build/utils/display/wrapper/types.d.ts.map +1 -0
  628. package/build/utils/display/wrapper/types.js +11 -0
  629. package/build/utils/display/wrapper/types.js.map +1 -0
  630. package/build/utils/e2eMetrics.d.ts +6 -0
  631. package/build/utils/e2eMetrics.d.ts.map +1 -0
  632. package/build/utils/e2eMetrics.js +27 -0
  633. package/build/utils/e2eMetrics.js.map +1 -0
  634. package/build/utils/hardware/hardware.d.ts +42 -0
  635. package/build/utils/hardware/hardware.d.ts.map +1 -0
  636. package/build/utils/hardware/hardware.js +127 -0
  637. package/build/utils/hardware/hardware.js.map +1 -0
  638. package/build/utils/hardware/index.d.ts +2 -0
  639. package/build/utils/hardware/index.d.ts.map +1 -0
  640. package/build/utils/hardware/index.js +2 -0
  641. package/build/utils/hardware/index.js.map +1 -0
  642. package/build/utils/miniappGlobals.d.ts +60 -0
  643. package/build/utils/miniappGlobals.d.ts.map +1 -0
  644. package/build/utils/miniappGlobals.js +224 -0
  645. package/build/utils/miniappGlobals.js.map +1 -0
  646. package/build/utils/permissions/MediaLibraryPermissions.d.ts +45 -0
  647. package/build/utils/permissions/MediaLibraryPermissions.d.ts.map +1 -0
  648. package/build/utils/permissions/MediaLibraryPermissions.js +145 -0
  649. package/build/utils/permissions/MediaLibraryPermissions.js.map +1 -0
  650. package/build/utils/permissions/galleryDisplayName.d.ts +8 -0
  651. package/build/utils/permissions/galleryDisplayName.d.ts.map +1 -0
  652. package/build/utils/permissions/galleryDisplayName.js +25 -0
  653. package/build/utils/permissions/galleryDisplayName.js.map +1 -0
  654. package/build/utils/storage/index.d.ts +3 -0
  655. package/build/utils/storage/index.d.ts.map +1 -0
  656. package/build/utils/storage/index.js +3 -0
  657. package/build/utils/storage/index.js.map +1 -0
  658. package/build/utils/storage/storage.d.ts +24 -0
  659. package/build/utils/storage/storage.d.ts.map +1 -0
  660. package/build/utils/storage/storage.js +85 -0
  661. package/build/utils/storage/storage.js.map +1 -0
  662. package/build/utils/storage/zip.d.ts +3 -0
  663. package/build/utils/storage/zip.d.ts.map +1 -0
  664. package/build/utils/storage/zip.js +14 -0
  665. package/build/utils/storage/zip.js.map +1 -0
  666. package/build/utils/timers/index.d.ts +3 -0
  667. package/build/utils/timers/index.d.ts.map +1 -0
  668. package/build/utils/timers/index.js +3 -0
  669. package/build/utils/timers/index.js.map +1 -0
  670. package/build/utils/timers/timers.d.ts +9 -0
  671. package/build/utils/timers/timers.d.ts.map +1 -0
  672. package/build/utils/timers/timers.js +105 -0
  673. package/build/utils/timers/timers.js.map +1 -0
  674. package/package.json +120 -0
  675. package/src/devtools.ts +13 -0
  676. package/src/engine.ts +173 -0
  677. package/src/facades/dev.ts +198 -0
  678. package/src/facades/display.ts +17 -0
  679. package/src/facades/displayMirror.ts +33 -0
  680. package/src/facades/gallery.ts +69 -0
  681. package/src/facades/glasses.ts +206 -0
  682. package/src/facades/glassesSettings.ts +56 -0
  683. package/src/facades/glassesWifi.ts +68 -0
  684. package/src/facades/miniapps.ts +55 -0
  685. package/src/facades/notifications.ts +15 -0
  686. package/src/facades/ota.ts +174 -0
  687. package/src/facades/pairing.ts +329 -0
  688. package/src/facades/permissions.ts +165 -0
  689. package/src/facades/phoneNotifications.ts +50 -0
  690. package/src/facades/reports.ts +185 -0
  691. package/src/facades/session.ts +55 -0
  692. package/src/facades/settings.ts +57 -0
  693. package/src/facades/speech.ts +38 -0
  694. package/src/index.ts +191 -0
  695. package/src/internal.ts +159 -0
  696. package/src/runtime/bootstrap.ts +118 -0
  697. package/src/runtime/config.ts +147 -0
  698. package/src/runtime/streamConfig.ts +34 -0
  699. package/src/services/AppRegistry.ts +1190 -0
  700. package/src/services/AudioCloudUplink.ts +50 -0
  701. package/src/services/AudioPlaybackService.ts +813 -0
  702. package/src/services/AutomaticReportResult.ts +48 -0
  703. package/src/services/BlobStore.ts +746 -0
  704. package/src/services/CaptionsTesterReportService.ts +107 -0
  705. package/src/services/CloudAudioSubscriptionSync.ts +41 -0
  706. package/src/services/CloudClientService.ts +689 -0
  707. package/src/services/CloudTranscriptE2EMetrics.ts +27 -0
  708. package/src/services/ConnectionCoordinator.ts +65 -0
  709. package/src/services/DevServerBridge.ts +332 -0
  710. package/src/services/DeviceEventRouter.ts +238 -0
  711. package/src/services/DeviceStoreHydration.ts +130 -0
  712. package/src/services/DisplayProcessor.ts +775 -0
  713. package/src/services/GlassesReadiness.ts +103 -0
  714. package/src/services/GlassesSettingsSync.ts +134 -0
  715. package/src/services/GlassesStatusProjection.ts +73 -0
  716. package/src/services/HeadingService.ts +98 -0
  717. package/src/services/LocalDisplayManager.ts +703 -0
  718. package/src/services/LocalMiniappRuntime.ts +4213 -0
  719. package/src/services/LocalSttFallbackCoordinator.ts +147 -0
  720. package/src/services/MentraJSCrashController.ts +189 -0
  721. package/src/services/MentraJSCrashloopReportService.ts +66 -0
  722. package/src/services/MentraJSLogPipeline.ts +166 -0
  723. package/src/services/MentraJSRouter.ts +557 -0
  724. package/src/services/MentraUIRouter.ts +244 -0
  725. package/src/services/MicStateCoordinator.ts +94 -0
  726. package/src/services/MiniappEngine.ts +143 -0
  727. package/src/services/MiniappLauncher.ts +340 -0
  728. package/src/services/MiniappRunningRegistry.ts +57 -0
  729. package/src/services/NativeHttpResponse.ts +6 -0
  730. package/src/services/NavigationHandlers.ts +423 -0
  731. package/src/services/NavigationService.ts +731 -0
  732. package/src/services/NotificationsEmitter.ts +48 -0
  733. package/src/services/OfflineSpeechModelService.ts +168 -0
  734. package/src/services/OtaInstallCoordinator.ts +1205 -0
  735. package/src/services/OtaService.ts +83 -0
  736. package/src/services/OtaUpdateCheckService.ts +386 -0
  737. package/src/services/PairingIdentity.ts +154 -0
  738. package/src/services/PhoneCalendarHelpers.ts +212 -0
  739. package/src/services/PhoneCalendarService.ts +42 -0
  740. package/src/services/PhoneCameraFovCoordinator.ts +195 -0
  741. package/src/services/PhoneLocationService.ts +122 -0
  742. package/src/services/PhoneNotificationsSync.ts +44 -0
  743. package/src/services/PhonePhotoCoordinator.ts +409 -0
  744. package/src/services/PhoneStreamCoordinator.ts +777 -0
  745. package/src/services/PhoneVideoCoordinator.ts +189 -0
  746. package/src/services/STTModelManager.ts +360 -0
  747. package/src/services/SceneRenderer.ts +292 -0
  748. package/src/services/SentenceTtsPipeline.ts +106 -0
  749. package/src/services/StreamLifecycleController.ts +213 -0
  750. package/src/services/TTSModelManager.ts +466 -0
  751. package/src/services/TranscriptionRouting.ts +37 -0
  752. package/src/services/WebviewBridge.ts +50 -0
  753. package/src/services/asg/GalleryMediaIntegrityReportService.ts +70 -0
  754. package/src/services/asg/asgCameraApi.ts +1695 -0
  755. package/src/services/asg/cameraRollExportCoordinator.ts +627 -0
  756. package/src/services/asg/cameraRollExportLedger.ts +122 -0
  757. package/src/services/asg/galleryMediaValidation.ts +183 -0
  758. package/src/services/asg/galleryNotices.ts +61 -0
  759. package/src/services/asg/galleryPathMigration.ts +22 -0
  760. package/src/services/asg/gallerySettingsService.ts +68 -0
  761. package/src/services/asg/gallerySyncNotifications.ts +236 -0
  762. package/src/services/asg/gallerySyncService.ts +2269 -0
  763. package/src/services/asg/galleryTransferLedger.ts +312 -0
  764. package/src/services/asg/localNetworkTransport.ts +173 -0
  765. package/src/services/asg/localStorageService.ts +652 -0
  766. package/src/services/asg/mediaProcessingQueue.ts +518 -0
  767. package/src/services/asgOtaVersionUrl.ts +41 -0
  768. package/src/services/blobPaths.ts +86 -0
  769. package/src/services/cloudStreamApi.ts +154 -0
  770. package/src/services/gallerySyncClock.ts +39 -0
  771. package/src/services/glassesClockSync.ts +115 -0
  772. package/src/services/manifestVersionGate.ts +43 -0
  773. package/src/services/mentraUiShim.ts +366 -0
  774. package/src/services/navigation/roadNameResolver.ts +112 -0
  775. package/src/services/navigation/routesApiCodec.ts +69 -0
  776. package/src/services/otaDisplayState.ts +115 -0
  777. package/src/services/otaInstallPolicy.ts +106 -0
  778. package/src/services/otaLegacyMapping.ts +71 -0
  779. package/src/services/slimStreamStatus.ts +28 -0
  780. package/src/stores/apps.ts +614 -0
  781. package/src/stores/bluetoothSettingKeys.ts +36 -0
  782. package/src/stores/cloudClientStatus.ts +37 -0
  783. package/src/stores/connection.ts +69 -0
  784. package/src/stores/core.ts +32 -0
  785. package/src/stores/display.ts +121 -0
  786. package/src/stores/gallerySync.ts +380 -0
  787. package/src/stores/glasses.ts +309 -0
  788. package/src/stores/settings.ts +1001 -0
  789. package/src/types/applet.ts +112 -0
  790. package/src/types/asg.ts +58 -0
  791. package/src/types/capabilities/even-realities-g1.ts +67 -0
  792. package/src/types/capabilities/even-realities-g2.ts +88 -0
  793. package/src/types/capabilities/mentra-display.ts +75 -0
  794. package/src/types/capabilities/mentra-live.ts +103 -0
  795. package/src/types/capabilities/none.ts +76 -0
  796. package/src/types/capabilities/simulated-glasses.ts +76 -0
  797. package/src/types/capabilities/vuzix-z100.ts +64 -0
  798. package/src/types/enums.ts +43 -0
  799. package/src/types/hardware.ts +203 -0
  800. package/src/types/index.ts +37 -0
  801. package/src/utils/GlobalEventEmitter.ts +14 -0
  802. package/src/utils/cloudClient/RnUdpAdapter.ts +47 -0
  803. package/src/utils/cloudClient/cloudSecureStore.ts +28 -0
  804. package/src/utils/debouncedPatch.ts +28 -0
  805. package/src/utils/devLogging.ts +140 -0
  806. package/src/utils/devMiniappLaunch.ts +68 -0
  807. package/src/utils/diagnosticContext.ts +155 -0
  808. package/src/utils/display/composer/ColumnComposer.ts +285 -0
  809. package/src/utils/display/composer/index.ts +15 -0
  810. package/src/utils/display/helpers/DisplayHelpers.ts +413 -0
  811. package/src/utils/display/helpers/ScrollView.ts +337 -0
  812. package/src/utils/display/helpers/index.ts +14 -0
  813. package/src/utils/display/index.ts +273 -0
  814. package/src/utils/display/measurer/TextMeasurer.ts +346 -0
  815. package/src/utils/display/measurer/index.ts +21 -0
  816. package/src/utils/display/measurer/script-detection.ts +218 -0
  817. package/src/utils/display/profiles/g1.ts +236 -0
  818. package/src/utils/display/profiles/g2.ts +39 -0
  819. package/src/utils/display/profiles/index.ts +28 -0
  820. package/src/utils/display/profiles/nex.ts +166 -0
  821. package/src/utils/display/profiles/types.ts +132 -0
  822. package/src/utils/display/profiles/z100.ts +198 -0
  823. package/src/utils/display/scene/degrade.ts +80 -0
  824. package/src/utils/display/scene/differ.ts +128 -0
  825. package/src/utils/display/scene/index.ts +21 -0
  826. package/src/utils/display/scene/process.ts +214 -0
  827. package/src/utils/display/scene/store.ts +0 -0
  828. package/src/utils/display/scene/types.ts +0 -0
  829. package/src/utils/display/wrapper/TextWrapper.ts +546 -0
  830. package/src/utils/display/wrapper/index.ts +15 -0
  831. package/src/utils/display/wrapper/types.ts +102 -0
  832. package/src/utils/e2eMetrics.ts +30 -0
  833. package/src/utils/hardware/hardware.ts +166 -0
  834. package/src/utils/hardware/index.ts +1 -0
  835. package/src/utils/miniappGlobals.ts +260 -0
  836. package/src/utils/permissions/MediaLibraryPermissions.ts +161 -0
  837. package/src/utils/permissions/galleryDisplayName.ts +28 -0
  838. package/src/utils/storage/index.ts +2 -0
  839. package/src/utils/storage/storage.ts +101 -0
  840. package/src/utils/storage/zip.ts +13 -0
  841. package/src/utils/timers/index.ts +2 -0
  842. package/src/utils/timers/timers.ts +139 -0
@@ -0,0 +1,1695 @@
1
+ /**
2
+ * ASG Camera Server API Client
3
+ * Provides methods to interact with the AsgCameraServer Java APIs
4
+ */
5
+
6
+ import * as RNFS from "@dr.pogodin/react-native-fs"
7
+
8
+ import {PhotoInfo, CaptureGroup, GalleryResponse, ServerStatus, HealthResponse} from "../../types/asg"
9
+ import {BgTimer} from "../../utils/timers"
10
+
11
+ import {localStorageService} from "./localStorageService"
12
+ import {validateDownloadedMediaFile} from "./galleryMediaValidation"
13
+ import {reportInvalidGalleryMedia} from "./GalleryMediaIntegrityReportService"
14
+ import {galleryTransferLedger} from "./galleryTransferLedger"
15
+ import {localNetworkTransport} from "./localNetworkTransport"
16
+
17
+ export interface GalleryCapabilities {
18
+ api_version: number
19
+ range_downloads: boolean
20
+ fixed_length_downloads: boolean
21
+ etag: boolean
22
+ sha256: boolean
23
+ recoverable_trash: boolean
24
+ idempotent_ack: boolean
25
+ selected_capture?: boolean
26
+ recommended_segment_bytes?: number
27
+ max_manifest_page_size?: number
28
+ }
29
+
30
+ type V3ManifestResponse = {
31
+ status: string
32
+ data: {
33
+ api_version: 3
34
+ captures: CaptureGroup[]
35
+ has_more: boolean
36
+ next_cursor?: string | null
37
+ total_count: number
38
+ server_time: number
39
+ }
40
+ }
41
+
42
+ export class AsgCameraApiClient {
43
+ private baseUrl: string
44
+ private port: number
45
+ private lastRequestTime: number = 0
46
+ private galleryCapabilities: GalleryCapabilities | null | undefined
47
+
48
+ constructor(serverUrl?: string, port: number = 8089) {
49
+ this.port = port
50
+ this.baseUrl = serverUrl || `http://localhost:${port}`
51
+ console.log(`[ASG Camera API] Client initialized with server: ${this.baseUrl}`)
52
+ }
53
+
54
+ private createTimeoutSignal(timeoutMs: number): AbortSignal {
55
+ const controller = new AbortController()
56
+ BgTimer.setTimeout(() => controller.abort(), timeoutMs)
57
+ return controller.signal
58
+ }
59
+
60
+ /**
61
+ * Set the server URL and port
62
+ */
63
+ setServer(serverUrl: string, port?: number) {
64
+ console.log(`[ASG Camera API] setServer called with serverUrl: ${serverUrl}, port: ${port}`)
65
+ const newPort = port || this.port
66
+ const newUrl = `http://${serverUrl.replace(/^https?:\/\//, "")}:${newPort}`
67
+
68
+ console.log(`[ASG Camera API] Constructed newUrl: ${newUrl}`)
69
+ // console.log(`[ASG Camera API] Current baseUrl: ${this.baseUrl}`)
70
+
71
+ // Only update if the URL actually changed
72
+ if (this.baseUrl !== newUrl) {
73
+ this.baseUrl = newUrl
74
+ this.port = newPort
75
+ this.galleryCapabilities = undefined
76
+ // console.log(`[ASG Camera API] Server changed from ${oldUrl} to ${this.baseUrl}`)
77
+ } else {
78
+ // console.log(`[ASG Camera API] Server URL unchanged: ${this.baseUrl}`)
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Get the current server URL
84
+ */
85
+ getServerUrl(): string {
86
+ return this.baseUrl
87
+ }
88
+
89
+ /**
90
+ * Rate limiting helper - ensures minimum delay between requests
91
+ */
92
+ private async rateLimit(minDelay: number = 500): Promise<void> {
93
+ const now = Date.now()
94
+ const timeSinceLastRequest = now - this.lastRequestTime
95
+
96
+ if (timeSinceLastRequest < minDelay) {
97
+ const delay = minDelay - timeSinceLastRequest
98
+ console.log(`[ASG Camera API] Rate limiting: waiting ${delay}ms`)
99
+ await new Promise<void>((resolve) => BgTimer.setTimeout(() => resolve(), delay))
100
+ }
101
+
102
+ this.lastRequestTime = Date.now()
103
+ }
104
+
105
+ /**
106
+ * Make a request to the ASG Camera Server with rate limiting and retry logic
107
+ */
108
+ private async makeRequest<T>(
109
+ endpoint: string,
110
+ options?: RequestInit,
111
+ retries: number = 5,
112
+ writeIntervalMs: number = 500,
113
+ ): Promise<T> {
114
+ const url = `${this.baseUrl}${endpoint}`
115
+ const method = options?.method || "GET"
116
+
117
+ console.log(`[ASG Camera API] makeRequest called with endpoint: ${endpoint}`)
118
+ console.log(`[ASG Camera API] Current baseUrl: ${this.baseUrl}`)
119
+ console.log(`[ASG Camera API] Full URL: ${url}`)
120
+ console.log(`[ASG Camera API] Method: ${method}`)
121
+ console.log(`[ASG Camera API] Retries remaining: ${retries}`)
122
+ console.log(`[ASG Camera API] Request options:`, {
123
+ method,
124
+ headers: options?.headers,
125
+ body: options?.body ? "Present" : "None",
126
+ })
127
+
128
+ const startTime = Date.now()
129
+
130
+ try {
131
+ // Apply rate limiting only for non-GET requests
132
+ if (method !== "GET" && writeIntervalMs > 0) {
133
+ await this.rateLimit(writeIntervalMs)
134
+ }
135
+
136
+ // Prepare headers - don't set Content-Type for GET requests
137
+ const headers: Record<string, string> = {
138
+ "Accept": "application/json",
139
+ "User-Agent": "MentraOS-Mobile/1.0",
140
+ }
141
+ if (method !== "GET") {
142
+ headers["Content-Type"] = "application/json"
143
+ }
144
+ if (options?.headers) {
145
+ Object.assign(headers, options.headers)
146
+ }
147
+
148
+ console.log(`[ASG Camera API] Making fetch request to: ${url}`)
149
+ console.log(`[ASG Camera API] Headers being sent:`, headers)
150
+
151
+ // N4: Add 30s timeout to all fetch calls in makeRequest
152
+ const response = await localNetworkTransport.fetch(url, {
153
+ headers,
154
+ ...options,
155
+ signal: options?.signal || this.createTimeoutSignal(30000),
156
+ })
157
+
158
+ const duration = Date.now() - startTime
159
+ console.log(`[ASG Camera API] Response received in ${duration}ms:`, {
160
+ status: response.status,
161
+ statusText: response.statusText,
162
+ contentType: response.headers.get("content-type"),
163
+ contentLength: response.headers.get("content-length"),
164
+ url: response.url,
165
+ })
166
+
167
+ if (!response.ok) {
168
+ console.error(`[ASG Camera API] HTTP Error ${response.status}: ${response.statusText}`)
169
+
170
+ // Handle rate limiting with retry
171
+ if (response.status === 429 && retries > 0) {
172
+ // N3: Cap individual retry delay at 10s
173
+ const retryDelay = Math.min(Math.pow(2, 6 - retries) * 1000, 10000)
174
+ console.log(`[ASG Camera API] Rate limited, retrying in ${retryDelay}ms (${retries} retries left)`)
175
+ await new Promise<void>((resolve) => BgTimer.setTimeout(() => resolve(), retryDelay))
176
+ return this.makeRequest<T>(endpoint, options, retries - 1, writeIntervalMs)
177
+ }
178
+
179
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`)
180
+ }
181
+
182
+ // Handle different response types
183
+ const contentType = response.headers.get("content-type")
184
+ console.log(`[ASG Camera API] Response content-type: ${contentType}`)
185
+
186
+ if (contentType?.includes("application/json")) {
187
+ const data = await response.json()
188
+ console.log(`[ASG Camera API] JSON response received`, {
189
+ endpoint,
190
+ status: data?.status,
191
+ apiVersion: data?.data?.api_version,
192
+ captureCount: data?.data?.captures?.length,
193
+ })
194
+ return data
195
+ } else if (contentType?.includes("image/") || contentType?.includes("application/octet-stream")) {
196
+ // For image responses and binary data (including AVIF), return the blob
197
+ const blob = await response.blob()
198
+ console.log(`[ASG Camera API] Binary/Image Response received:`, {
199
+ size: blob.size,
200
+ type: blob.type,
201
+ })
202
+
203
+ // Quick check if this might be an AVIF file
204
+ if (contentType?.includes("application/octet-stream") && blob.size > 12) {
205
+ const arrayBuffer = await blob.arrayBuffer()
206
+ const bytes = new Uint8Array(arrayBuffer.slice(4, 12))
207
+ const ftypSignature = String.fromCharCode(...bytes)
208
+ if (ftypSignature === "ftypavif") {
209
+ console.log(`[ASG Camera API] Detected AVIF file in response`)
210
+ }
211
+ // Return a new blob since we consumed the original
212
+ return new Blob([arrayBuffer], {type: blob.type}) as T
213
+ }
214
+
215
+ return blob as T
216
+ } else {
217
+ // For text responses
218
+ const text = await response.text()
219
+ console.log(
220
+ `[ASG Camera API] Text Response received:`,
221
+ text.substring(0, 200) + (text.length > 200 ? "..." : ""),
222
+ )
223
+ return text as T
224
+ }
225
+ } catch (error) {
226
+ const duration = Date.now() - startTime
227
+ console.error(`[ASG Camera API] Error (${endpoint}) after ${duration}ms:`, error)
228
+ console.error(`[ASG Camera API] Error details:`, {
229
+ endpoint,
230
+ url,
231
+ method,
232
+ duration,
233
+ errorMessage: error instanceof Error ? error.message : String(error),
234
+ errorStack: error instanceof Error ? error.stack : undefined,
235
+ })
236
+ throw error
237
+ }
238
+ }
239
+
240
+ /**
241
+ * Take a picture using the ASG camera
242
+ */
243
+ async takePicture(): Promise<{message: string}> {
244
+ console.log(`[ASG Camera API] Taking picture...`)
245
+ return this.makeRequest<{message: string}>("/api/take-picture", {
246
+ method: "POST",
247
+ })
248
+ }
249
+
250
+ /**
251
+ * Get the latest photo as a blob
252
+ */
253
+ async getLatestPhoto(): Promise<Blob> {
254
+ console.log(`[ASG Camera API] Getting latest photo...`)
255
+ return this.makeRequest<Blob>("/api/latest-photo")
256
+ }
257
+
258
+ /**
259
+ * Get the latest photo as a data URL
260
+ */
261
+ async getLatestPhotoAsDataUrl(): Promise<string> {
262
+ console.log(`[ASG Camera API] Getting latest photo as data URL...`)
263
+ const blob = await this.getLatestPhoto()
264
+ return new Promise((resolve, reject) => {
265
+ const reader = new FileReader()
266
+ reader.onload = () => resolve(reader.result as string)
267
+ reader.onerror = reject
268
+ reader.readAsDataURL(blob)
269
+ })
270
+ }
271
+
272
+ /**
273
+ * Get gallery photos from the server with optional pagination
274
+ */
275
+ async getGallery(limit?: number, offset?: number): Promise<GalleryResponse> {
276
+ console.log(`[ASG Camera API] getGallery called with limit=${limit}, offset=${offset}`)
277
+ // console.log(`[ASG Camera API] Current baseUrl: ${this.baseUrl}`)
278
+
279
+ // Build URL with optional query parameters
280
+ let galleryUrl = `${this.baseUrl}/api/gallery`
281
+ const params = new URLSearchParams()
282
+ if (limit !== undefined) params.append("limit", limit.toString())
283
+ if (offset !== undefined) params.append("offset", offset.toString())
284
+ if (params.toString()) galleryUrl += `?${params.toString()}`
285
+
286
+ console.log(`[ASG Camera API] Full gallery URL: ${galleryUrl}`)
287
+
288
+ // Use browser-like headers since we know the browser works
289
+ try {
290
+ console.log(`[ASG Camera API] Making direct fetch to gallery endpoint`)
291
+ const response = await localNetworkTransport.fetch(galleryUrl, {
292
+ method: "GET",
293
+ headers: {
294
+ "Accept": "application/json",
295
+ "User-Agent": "MentraOS-Mobile/1.0",
296
+ },
297
+ signal: this.createTimeoutSignal(10000), // 10 second timeout
298
+ })
299
+
300
+ console.log(`[ASG Camera API] Response status: ${response.status}`)
301
+
302
+ if (!response.ok) {
303
+ throw new Error(`Gallery endpoint returned: ${response.status}`)
304
+ }
305
+
306
+ const responseText = await response.text()
307
+ console.log(`[ASG Camera API] Raw response:`, responseText.substring(0, 1000))
308
+
309
+ let data: any
310
+ try {
311
+ data = JSON.parse(responseText)
312
+ } catch (parseError) {
313
+ console.log(`[ASG Camera API] Failed to parse JSON:`, parseError)
314
+ throw new Error("Invalid JSON response from gallery endpoint")
315
+ }
316
+
317
+ // Handle the exact response format we see from browser
318
+ if (data && data.status === "success" && data.data?.photos) {
319
+ console.log(`[ASG Camera API] Found ${data.data.photos.length} photos (total: ${data.data.total_count})`)
320
+
321
+ // Map photos to ensure proper URL construction
322
+ const photos = data.data.photos.map((photo: any) => ({
323
+ ...photo,
324
+ url: this.constructPhotoUrl(photo.url || photo.name),
325
+ download: this.constructDownloadUrl(photo.download || photo.name),
326
+ }))
327
+
328
+ return {
329
+ status: "success",
330
+ data: {
331
+ photos,
332
+ total_count: data.data.total_count,
333
+ returned_count: data.data.returned_count,
334
+ has_more: data.data.has_more,
335
+ offset: data.data.offset,
336
+ limit: data.data.limit,
337
+ },
338
+ } as GalleryResponse
339
+ } else {
340
+ console.log(`[ASG Camera API] Invalid response structure:`, data)
341
+ throw new Error("Invalid response structure from gallery endpoint")
342
+ }
343
+ } catch (error) {
344
+ console.log(`[ASG Camera API] Gallery request failed:`, error)
345
+ throw error
346
+ }
347
+ }
348
+
349
+ async deleteGalleryPhoto(photoId: string): Promise<any> {
350
+ const response = await this.makeRequest<any>(`/api/gallery/${photoId}`, {
351
+ method: "DELETE",
352
+ })
353
+ console.log("Photo deleted successfully:", photoId)
354
+ return response
355
+ }
356
+
357
+ /**
358
+ * Get the gallery photos array with proper URL construction
359
+ */
360
+ async getGalleryPhotos(
361
+ limit?: number,
362
+ offset?: number,
363
+ ): Promise<{
364
+ photos: PhotoInfo[]
365
+ hasMore: boolean
366
+ totalCount: number
367
+ }> {
368
+ console.log(`[ASG Camera API] Getting gallery photos with limit=${limit}, offset=${offset}...`)
369
+ try {
370
+ const response = await this.getGallery(limit, offset)
371
+ console.log(`[ASG Camera API] Gallery response:`, response)
372
+
373
+ if (!response.data || !response.data.photos) {
374
+ console.warn(`[ASG Camera API] Invalid gallery response structure:`, response)
375
+ return {photos: [], hasMore: false, totalCount: 0}
376
+ }
377
+
378
+ const photos = response.data.photos
379
+ console.log(`[ASG Camera API] Found ${photos.length} photos (total: ${(response.data as any).total_count})`)
380
+
381
+ // Ensure each photo has proper URLs and detect AVIF files
382
+ const processedPhotos = photos.map((photo) => {
383
+ // Check if filename suggests AVIF (no extension or .avif)
384
+ const mightBeAvif = !photo.name.includes(".") || photo.name.match(/\.(avif|avifs)$/i)
385
+
386
+ return {
387
+ ...photo,
388
+ url: this.constructPhotoUrl(photo.name),
389
+ download: this.constructDownloadUrl(photo.name),
390
+ mime_type: photo.mime_type || (mightBeAvif ? "image/avif" : undefined),
391
+ }
392
+ })
393
+
394
+ console.log(`[ASG Camera API] Processed photos:`, processedPhotos)
395
+ return {
396
+ photos: processedPhotos,
397
+ hasMore: (response.data as any).has_more || false,
398
+ totalCount: (response.data as any).total_count || photos.length,
399
+ }
400
+ } catch (error) {
401
+ console.error(`[ASG Camera API] Error getting gallery photos:`, error)
402
+ throw error
403
+ }
404
+ }
405
+
406
+ /**
407
+ * Discover available endpoints on the server
408
+ */
409
+ async discoverEndpoints(): Promise<string[]> {
410
+ const availableEndpoints: string[] = []
411
+ const testEndpoints = [
412
+ "/",
413
+ "/api",
414
+ "/api/health",
415
+ "/api/status",
416
+ "/api/gallery",
417
+ "/gallery",
418
+ "/api/photos",
419
+ "/photos",
420
+ "/api/images",
421
+ "/images",
422
+ "/api/take-picture",
423
+ "/api/latest-photo",
424
+ ]
425
+
426
+ console.log(`[ASG Camera API] Discovering available endpoints...`)
427
+
428
+ for (const endpoint of testEndpoints) {
429
+ try {
430
+ console.log(`[ASG Camera API] Testing endpoint: ${endpoint}`)
431
+ const response = await localNetworkTransport.fetch(`${this.baseUrl}${endpoint}`, {
432
+ method: "HEAD",
433
+ headers: {
434
+ "Accept": "*/*",
435
+ "User-Agent": "MentraOS-Mobile/1.0",
436
+ },
437
+ signal: this.createTimeoutSignal(5000),
438
+ })
439
+
440
+ if (response.ok) {
441
+ availableEndpoints.push(endpoint)
442
+ console.log(`[ASG Camera API] Found endpoint: ${endpoint} (${response.status})`)
443
+ } else {
444
+ console.log(`[ASG Camera API] Endpoint ${endpoint} returned: ${response.status}`)
445
+ }
446
+ } catch (error) {
447
+ console.log(`[ASG Camera API] Endpoint ${endpoint} failed:`, error)
448
+ // For /api/gallery specifically, let's try a GET request to see if it's a HEAD request issue
449
+ if (endpoint === "/api/gallery") {
450
+ try {
451
+ console.log(`[ASG Camera API] Trying GET request for /api/gallery...`)
452
+ const getResponse = await localNetworkTransport.fetch(`${this.baseUrl}${endpoint}`, {
453
+ method: "GET",
454
+ headers: {
455
+ "Accept": "application/json",
456
+ "User-Agent": "MentraOS-Mobile/1.0",
457
+ },
458
+ signal: this.createTimeoutSignal(5000),
459
+ })
460
+ console.log(`[ASG Camera API] GET /api/gallery status: ${getResponse.status}`)
461
+ if (getResponse.ok) {
462
+ console.log(`[ASG Camera API] GET /api/gallery works! Adding to available endpoints`)
463
+ availableEndpoints.push(endpoint)
464
+ }
465
+ } catch (getError) {
466
+ console.log(`[ASG Camera API] GET /api/gallery also failed:`, getError)
467
+ }
468
+ }
469
+ }
470
+ }
471
+
472
+ console.log(`[ASG Camera API] Available endpoints:`, availableEndpoints)
473
+ return availableEndpoints
474
+ }
475
+
476
+ /**
477
+ * Construct a photo URL for a given filename
478
+ */
479
+ private constructPhotoUrl(filename: string): string {
480
+ return `${this.baseUrl}/api/photo?file=${encodeURIComponent(filename)}`
481
+ }
482
+
483
+ /**
484
+ * Construct a download URL for a given filename
485
+ */
486
+ private constructDownloadUrl(filename: string): string {
487
+ return `${this.baseUrl}/api/download?file=${encodeURIComponent(filename)}`
488
+ }
489
+
490
+ /**
491
+ * Get a specific photo by filename
492
+ */
493
+ async getPhoto(filename: string): Promise<Blob> {
494
+ console.log(`[ASG Camera API] Getting photo: ${filename}`)
495
+ return this.makeRequest<Blob>(`/api/photo?file=${encodeURIComponent(filename)}`)
496
+ }
497
+
498
+ /**
499
+ * Get a specific photo as a data URL
500
+ */
501
+ async getPhotoAsDataUrl(filename: string): Promise<string> {
502
+ console.log(`[ASG Camera API] Getting photo as data URL: ${filename}`)
503
+ const blob = await this.getPhoto(filename)
504
+ return new Promise((resolve, reject) => {
505
+ const reader = new FileReader()
506
+ reader.onload = () => resolve(reader.result as string)
507
+ reader.onerror = reject
508
+ reader.readAsDataURL(blob)
509
+ })
510
+ }
511
+
512
+ /**
513
+ * Download a photo (returns download URL)
514
+ */
515
+ async downloadPhoto(filename: string): Promise<string> {
516
+ console.log(`[ASG Camera API] Downloading photo: ${filename}`)
517
+ const downloadUrl = `${this.baseUrl}/api/download?file=${encodeURIComponent(filename)}`
518
+ console.log(`[ASG Camera API] Download URL: ${downloadUrl}`)
519
+ return downloadUrl
520
+ }
521
+
522
+ /**
523
+ * Get server status information
524
+ */
525
+ async getStatus(): Promise<ServerStatus> {
526
+ console.log(`[ASG Camera API] Getting server status...`)
527
+ return this.makeRequest<ServerStatus>("/api/status")
528
+ }
529
+
530
+ /**
531
+ * Get server health check
532
+ */
533
+ async getHealth(): Promise<HealthResponse> {
534
+ console.log(`[ASG Camera API] Getting server health...`)
535
+ return this.makeRequest<HealthResponse>("/api/health")
536
+ }
537
+
538
+ /**
539
+ * Get the index page (for testing)
540
+ */
541
+ async getIndexPage(): Promise<string> {
542
+ console.log(`[ASG Camera API] Getting index page...`)
543
+ return this.makeRequest<string>("/")
544
+ }
545
+
546
+ /**
547
+ * Check if the server is reachable (simple ping)
548
+ */
549
+ async isServerReachable(): Promise<boolean> {
550
+ try {
551
+ console.log(`[ASG Camera API] Checking server reachability...`)
552
+ // Use a simple HEAD request to check reachability
553
+ const controller = new AbortController()
554
+ const timeoutId = BgTimer.setTimeout(() => controller.abort(), 3000) // 3 second timeout
555
+
556
+ const response = await localNetworkTransport.fetch(`${this.baseUrl}/api/health`, {
557
+ method: "HEAD",
558
+ signal: controller.signal,
559
+ })
560
+
561
+ BgTimer.clearTimeout(timeoutId)
562
+ console.log(`[ASG Camera API] Server is reachable`)
563
+ return response.ok
564
+ } catch (error) {
565
+ console.log(`[ASG Camera API] Server is not reachable:`, error)
566
+ return false
567
+ }
568
+ }
569
+
570
+ /**
571
+ * Get comprehensive server information
572
+ */
573
+ async getServerInfo(): Promise<{
574
+ reachable: boolean
575
+ status?: ServerStatus
576
+ health?: HealthResponse
577
+ error?: string
578
+ }> {
579
+ try {
580
+ const [status, health] = await Promise.all([this.getStatus(), this.getHealth()])
581
+
582
+ return {
583
+ reachable: true,
584
+ status,
585
+ health,
586
+ }
587
+ } catch (error) {
588
+ return {
589
+ reachable: false,
590
+ error: error instanceof Error ? error.message : "Unknown error",
591
+ }
592
+ }
593
+ }
594
+
595
+ /**
596
+ * Sync with server to get changed files since last sync
597
+ */
598
+ async syncWithServer(
599
+ clientId: string,
600
+ lastSyncTime?: number,
601
+ includeThumbnails: boolean = false,
602
+ ): Promise<{
603
+ status: string
604
+ data: {
605
+ api_version?: number
606
+ client_id: string
607
+ captures?: CaptureGroup[]
608
+ changed_files: PhotoInfo[]
609
+ deleted_files: string[]
610
+ server_time: number
611
+ total_changed: number
612
+ total_size: number
613
+ }
614
+ }> {
615
+ const params = new URLSearchParams({
616
+ client_id: clientId,
617
+ include_thumbnails: includeThumbnails.toString(),
618
+ })
619
+
620
+ if (lastSyncTime) {
621
+ params.append("last_sync_time", lastSyncTime.toString())
622
+ }
623
+
624
+ const response = await this.makeRequest(`/api/sync?${params.toString()}`, {
625
+ method: "GET",
626
+ })
627
+
628
+ return response as {
629
+ status: string
630
+ data: {
631
+ api_version?: number
632
+ client_id: string
633
+ captures?: CaptureGroup[]
634
+ changed_files: PhotoInfo[]
635
+ deleted_files: string[]
636
+ server_time: number
637
+ total_changed: number
638
+ total_size: number
639
+ }
640
+ }
641
+ }
642
+
643
+ /** Probe the additive gallery protocol. A 404 cleanly falls back to legacy v2. */
644
+ async getGalleryCapabilities(): Promise<GalleryCapabilities | null> {
645
+ if (this.galleryCapabilities !== undefined) return this.galleryCapabilities
646
+ try {
647
+ const response = await localNetworkTransport.fetch(`${this.baseUrl}/api/v3/capabilities`, {
648
+ headers: {"Accept": "application/json", "User-Agent": "MentraOS-Mobile/1.0"},
649
+ signal: this.createTimeoutSignal(5000),
650
+ })
651
+ if (response.status === 404) {
652
+ this.galleryCapabilities = null
653
+ return null
654
+ }
655
+ if (!response.ok) throw new Error(`HTTP ${response.status}`)
656
+ const payload = await response.json()
657
+ const capabilities = (payload.data || payload) as GalleryCapabilities
658
+ this.galleryCapabilities = capabilities.api_version >= 3 ? capabilities : null
659
+ return this.galleryCapabilities
660
+ } catch (error) {
661
+ // A failed probe must not break old firmware. Do not permanently cache transient failures.
662
+ console.warn("[ASG Camera API] v3 capability probe failed; using legacy sync for this attempt", error)
663
+ return null
664
+ }
665
+ }
666
+
667
+ /** Fetch all lightweight v3 manifest pages, newest first. */
668
+ async getV3Manifest(): Promise<V3ManifestResponse["data"] | null> {
669
+ const capabilities = await this.getGalleryCapabilities()
670
+ if (!capabilities) return null
671
+
672
+ const captures: CaptureGroup[] = []
673
+ let cursor: string | undefined
674
+ let serverTime = Date.now()
675
+ let totalCount = 0
676
+ do {
677
+ const params = new URLSearchParams({limit: String(capabilities.max_manifest_page_size || 100)})
678
+ if (cursor) params.set("cursor", cursor)
679
+ const response = await this.makeRequest<V3ManifestResponse>(`/api/v3/manifest?${params.toString()}`)
680
+ const data = response.data || (response as unknown as V3ManifestResponse["data"])
681
+ captures.push(...(data.captures || []))
682
+ serverTime = data.server_time || serverTime
683
+ totalCount = data.total_count || captures.length
684
+ cursor = data.has_more && data.next_cursor ? data.next_cursor : undefined
685
+ } while (cursor)
686
+
687
+ return {
688
+ api_version: 3,
689
+ captures,
690
+ has_more: false,
691
+ next_cursor: null,
692
+ total_count: totalCount,
693
+ server_time: serverTime,
694
+ }
695
+ }
696
+
697
+ async getV3FileHash(fileName: string): Promise<{sha256: string; etag: string; size: number}> {
698
+ const response = await localNetworkTransport.fetch(
699
+ `${this.baseUrl}/api/v3/hash?file=${encodeURIComponent(fileName)}`,
700
+ {
701
+ headers: {"Accept": "application/json", "User-Agent": "MentraOS-Mobile/1.0"},
702
+ signal: this.createTimeoutSignal(10 * 60 * 1000),
703
+ },
704
+ 10 * 60 * 1000,
705
+ )
706
+ if (!response.ok) throw new Error(`Hash request failed: HTTP ${response.status}`)
707
+ const payload = await response.json()
708
+ return (payload.data || payload) as {sha256: string; etag: string; size: number}
709
+ }
710
+
711
+ async acknowledgeCapture(captureId: string, ackId: string): Promise<void> {
712
+ const response = await this.makeRequest<any>(
713
+ "/api/v3/ack",
714
+ {
715
+ method: "POST",
716
+ body: JSON.stringify({capture_id: captureId, ack_id: ackId}),
717
+ },
718
+ 5,
719
+ 0,
720
+ )
721
+ const data = response.data || response
722
+ if (!data.success && !data.already_trashed) {
723
+ throw new Error(data.message || `Glasses did not acknowledge ${captureId}`)
724
+ }
725
+ }
726
+
727
+ async restoreCapture(captureId: string): Promise<void> {
728
+ const response = await this.makeRequest<any>(
729
+ "/api/v3/restore",
730
+ {
731
+ method: "POST",
732
+ body: JSON.stringify({capture_id: captureId}),
733
+ },
734
+ 5,
735
+ 0,
736
+ )
737
+ const data = response.data || response
738
+ if (!data.success) throw new Error(data.message || `Glasses did not restore ${captureId}`)
739
+ }
740
+
741
+ /**
742
+ * Batch sync files from server with controlled concurrency.
743
+ * Used by the legacy executeDownload path for old asg_client firmware
744
+ * that doesn't send api_version=2.
745
+ */
746
+ async batchSyncFiles(
747
+ files: PhotoInfo[],
748
+ includeThumbnails: boolean = false,
749
+ onProgress?: (
750
+ current: number,
751
+ total: number,
752
+ fileName: string,
753
+ fileProgress?: number,
754
+ downloadedFile?: PhotoInfo,
755
+ ) => void,
756
+ abortSignal?: AbortSignal,
757
+ ): Promise<{
758
+ downloaded: PhotoInfo[]
759
+ failed: string[]
760
+ total_size: number
761
+ }> {
762
+ const results = {
763
+ downloaded: [] as PhotoInfo[],
764
+ failed: [] as string[],
765
+ total_size: 0,
766
+ }
767
+
768
+ // Process files in parallel batches for better performance
769
+ // Use controlled concurrency to avoid overwhelming the network
770
+ const CONCURRENCY_LIMIT = 1
771
+
772
+ // Process files in batches
773
+ for (let i = 0; i < files.length; i += CONCURRENCY_LIMIT) {
774
+ const batch = files.slice(i, i + CONCURRENCY_LIMIT)
775
+ console.log(
776
+ `[ASG Camera API] Processing batch ${Math.floor(i / CONCURRENCY_LIMIT) + 1}/${Math.ceil(
777
+ files.length / CONCURRENCY_LIMIT,
778
+ )}: ${batch.length} files`,
779
+ )
780
+
781
+ // Process batch in parallel
782
+ const batchPromises = batch.map(async (file, batchIndex) => {
783
+ const globalIndex = i + batchIndex
784
+
785
+ // Report progress if callback provided - start of this file (0%)
786
+ if (onProgress) {
787
+ onProgress(globalIndex + 1, files.length, file.name, 0)
788
+ }
789
+
790
+ try {
791
+ console.log(`[ASG Camera API] Downloading file ${globalIndex + 1}/${files.length}: ${file.name}`)
792
+
793
+ // Download file with progress tracking
794
+ const fileData = await this.downloadFile(
795
+ file.name,
796
+ includeThumbnails,
797
+ (fileProgress) => {
798
+ if (onProgress) {
799
+ onProgress(globalIndex + 1, files.length, file.name, fileProgress)
800
+ }
801
+ },
802
+ abortSignal,
803
+ file.size, // Pass expected size for validation when Content-Length is missing
804
+ )
805
+
806
+ // Combine file info with downloaded file paths
807
+ const downloadedFile = {
808
+ ...file,
809
+ filePath: fileData.filePath,
810
+ thumbnailPath: fileData.thumbnailPath,
811
+ mime_type: fileData.mime_type || file.mime_type,
812
+ }
813
+
814
+ console.log(`[ASG Camera API] Successfully downloaded: ${file.name}`)
815
+
816
+ // Notify progress callback that this file is complete with file info
817
+ if (onProgress) {
818
+ onProgress(globalIndex + 1, files.length, file.name, 100, downloadedFile)
819
+ }
820
+
821
+ // Don't delete from glasses here — deletion is deferred until after
822
+ // processing completes (in mediaProcessingQueue) to avoid data loss on crash.
823
+
824
+ return {downloadedFile, fileSize: file.size}
825
+ } catch (error: any) {
826
+ // Re-throw cancellation so it terminates the entire batch
827
+ if (error?.message === "Sync cancelled") {
828
+ throw error
829
+ }
830
+ console.error(`[ASG Camera API] Failed to download ${file.name}:`, error)
831
+ return {error: file.name}
832
+ }
833
+ })
834
+
835
+ // Wait for batch to complete
836
+ const batchResults = await Promise.all(batchPromises)
837
+
838
+ // Process batch results
839
+ for (const result of batchResults) {
840
+ if ("downloadedFile" in result && result.downloadedFile) {
841
+ results.downloaded.push(result.downloadedFile)
842
+ results.total_size += result.fileSize || 0
843
+ } else if ("error" in result) {
844
+ results.failed.push(result.error)
845
+ }
846
+ }
847
+ }
848
+
849
+ console.log(
850
+ `[ASG Camera API] Batch sync completed: ${results.downloaded.length} downloaded, ${results.failed.length} failed`,
851
+ )
852
+ return results
853
+ }
854
+
855
+ /**
856
+ * Download all files in a capture group sequentially.
857
+ * Reports aggregate byte progress across all files in the group.
858
+ */
859
+ private async downloadResumableCaptureFile(
860
+ captureId: string,
861
+ file: CaptureGroup["files"][number],
862
+ localFilePath: string,
863
+ onProgress?: (bytesDownloaded: number) => void,
864
+ abortSignal?: AbortSignal,
865
+ ): Promise<void> {
866
+ const segmentBytes = 16 * 1024 * 1024
867
+ const segmentCount = Math.max(1, Math.ceil(file.size / segmentBytes))
868
+ const generation = (file.etag || `${file.size}`).replace(/[^A-Za-z0-9_-]/g, "").slice(0, 48) || "legacy"
869
+ const partPath = (index: number) => `${localFilePath}.partial.${generation}.${index}`
870
+ const assemblingPath = `${localFilePath}.assembling.${generation}`
871
+ const transferStartedAt = Date.now()
872
+ let networkDurationMs = 0
873
+ let networkBytes = 0
874
+
875
+ if (await RNFS.exists(localFilePath)) {
876
+ const existing = await RNFS.stat(localFilePath)
877
+ if (existing.size === file.size) {
878
+ if (file.etag) {
879
+ const remoteHash = file.sha256 ? {sha256: file.sha256, etag: file.etag} : await this.getV3FileHash(file.name)
880
+ if (remoteHash.etag && remoteHash.etag !== file.etag) {
881
+ throw new Error("Source generation changed before existing-file verification")
882
+ }
883
+ const localHash = await RNFS.hash(localFilePath, "sha256")
884
+ if (localHash.toLowerCase() === remoteHash.sha256.toLowerCase()) {
885
+ galleryTransferLedger.markFileHash(captureId, file.name, localHash.toLowerCase())
886
+ onProgress?.(file.size)
887
+ return
888
+ }
889
+ console.warn(`[ASG Camera API] Existing file failed SHA-256 verification; replacing ${file.name}`)
890
+ } else {
891
+ // Legacy servers have no generation/hash endpoint. Preserve old-client
892
+ // compatibility and let the media validator below reject corrupt data.
893
+ onProgress?.(file.size)
894
+ return
895
+ }
896
+ }
897
+ await RNFS.moveFile(localFilePath, `${localFilePath}.recovery.${Date.now()}`).catch(() => {})
898
+ }
899
+
900
+ let completedBytes = 0
901
+ let resumedSegmentCount = 0
902
+ for (let index = 0; index < segmentCount; index++) {
903
+ const start = index * segmentBytes
904
+ const expectedLength = Math.min(segmentBytes, file.size - start)
905
+ if (await RNFS.exists(partPath(index))) {
906
+ const stat = await RNFS.stat(partPath(index))
907
+ if (stat.size === expectedLength) {
908
+ completedBytes += expectedLength
909
+ resumedSegmentCount++
910
+ galleryTransferLedger.markSegmentComplete(captureId, file.name, index)
911
+ continue
912
+ }
913
+ await RNFS.unlink(partPath(index)).catch(() => {})
914
+ }
915
+
916
+ let completed = false
917
+ let lastError: unknown
918
+ for (let attempt = 0; attempt < 5 && !completed; attempt++) {
919
+ if (abortSignal?.aborted) throw new Error("Sync cancelled")
920
+ await RNFS.unlink(partPath(index)).catch(() => {})
921
+ const end = start + expectedLength - 1
922
+ let responseHeaders: Record<string, string> = {}
923
+ const {jobId, promise} = localNetworkTransport.downloadFile({
924
+ fromUrl: `${this.baseUrl}/api/download?file=${encodeURIComponent(file.name)}`,
925
+ toFile: partPath(index),
926
+ headers: {
927
+ "Accept": "*/*",
928
+ "User-Agent": "MentraOS-Mobile/1.0",
929
+ "Range": `bytes=${start}-${end}`,
930
+ ...(file.etag ? {"If-Range": file.etag} : {}),
931
+ },
932
+ connectionTimeout: 300000,
933
+ readTimeout: 300000,
934
+ backgroundTimeout: 24 * 60 * 60 * 1000,
935
+ progressDivider: 2,
936
+ progressInterval: 250,
937
+ begin: (response) => {
938
+ responseHeaders = response.headers || {}
939
+ },
940
+ progress: (progress) => onProgress?.(completedBytes + (progress.bytesWritten || 0)),
941
+ })
942
+
943
+ let abortPollTimer: number | undefined
944
+ if (abortSignal) {
945
+ abortPollTimer = BgTimer.setInterval(() => {
946
+ if (abortSignal.aborted) localNetworkTransport.stopDownload(jobId)
947
+ }, 500)
948
+ }
949
+
950
+ const attemptStartedAt = Date.now()
951
+ try {
952
+ const result = await promise
953
+ const attemptDurationMs = Math.max(1, Date.now() - attemptStartedAt)
954
+ networkDurationMs += attemptDurationMs
955
+ networkBytes += result.bytesWritten || 0
956
+ if (abortSignal?.aborted) throw new Error("Sync cancelled")
957
+ const header = (name: string): string | undefined => {
958
+ const key = Object.keys(responseHeaders).find((candidate) => candidate.toLowerCase() === name.toLowerCase())
959
+ return key ? responseHeaders[key] : undefined
960
+ }
961
+
962
+ if (result.statusCode === 200 && index === 0 && result.bytesWritten === file.size) {
963
+ // Older servers ignore Range. A full fixed/chunked response is still compatible;
964
+ // commit it through the same staging path and skip the remaining segments.
965
+ await RNFS.unlink(assemblingPath).catch(() => {})
966
+ await RNFS.moveFile(partPath(index), assemblingPath)
967
+ completedBytes = file.size
968
+ completed = true
969
+ break
970
+ }
971
+ if (result.statusCode !== 206) throw new Error(`HTTP ${result.statusCode}; expected 206`)
972
+ if (result.bytesWritten !== expectedLength) {
973
+ throw new Error(`Segment ${index} truncated: ${result.bytesWritten}/${expectedLength} bytes`)
974
+ }
975
+ const contentRange = header("content-range")
976
+ if (contentRange !== `bytes ${start}-${end}/${file.size}`) {
977
+ throw new Error(`Invalid Content-Range: ${contentRange || "missing"}`)
978
+ }
979
+ const responseEtag = header("etag")
980
+ if (file.etag && responseEtag && responseEtag !== file.etag) {
981
+ throw new Error("Source generation changed during transfer")
982
+ }
983
+ galleryTransferLedger.markSegmentComplete(captureId, file.name, index)
984
+ completedBytes += expectedLength
985
+ completed = true
986
+ console.log("[ASG Camera API] Gallery segment complete", {
987
+ captureId,
988
+ file: file.name,
989
+ segment: index,
990
+ segmentCount,
991
+ attempt: attempt + 1,
992
+ status: result.statusCode,
993
+ bytes: result.bytesWritten,
994
+ durationMs: attemptDurationMs,
995
+ throughputMbps: (((result.bytesWritten || 0) * 8) / attemptDurationMs / 1000).toFixed(2),
996
+ etag: file.etag,
997
+ })
998
+ } catch (error) {
999
+ lastError = error
1000
+ if (error instanceof Error && error.message === "Sync cancelled") throw error
1001
+ console.warn("[ASG Camera API] Gallery segment attempt failed", {
1002
+ captureId,
1003
+ file: file.name,
1004
+ segment: index,
1005
+ segmentCount,
1006
+ attempt: attempt + 1,
1007
+ rangeStart: start,
1008
+ rangeEnd: end,
1009
+ message: error instanceof Error ? error.message : String(error),
1010
+ })
1011
+ await RNFS.unlink(partPath(index)).catch(() => {})
1012
+ if (attempt < 4) {
1013
+ const delay = Math.min(1000 * 2 ** attempt, 10000) + Math.floor(Math.random() * 500)
1014
+ await new Promise<void>((resolve) => BgTimer.setTimeout(resolve, delay))
1015
+ }
1016
+ } finally {
1017
+ if (abortPollTimer !== undefined) BgTimer.clearInterval(abortPollTimer)
1018
+ }
1019
+ }
1020
+ if (!completed) throw lastError || new Error(`Segment ${index} failed`)
1021
+ if (completedBytes === file.size && (await RNFS.exists(assemblingPath))) break
1022
+ }
1023
+
1024
+ if (!(await RNFS.exists(assemblingPath))) {
1025
+ await RNFS.unlink(assemblingPath).catch(() => {})
1026
+ await RNFS.writeFile(assemblingPath, "", "utf8")
1027
+ for (let index = 0; index < segmentCount; index++) {
1028
+ const data = await RNFS.readFile(partPath(index), "base64")
1029
+ await RNFS.appendFile(assemblingPath, data, "base64")
1030
+ }
1031
+ }
1032
+
1033
+ const assembledStat = await RNFS.stat(assemblingPath)
1034
+ if (assembledStat.size !== file.size) {
1035
+ await RNFS.moveFile(assemblingPath, `${assemblingPath}.invalid.${Date.now()}`).catch(() => {})
1036
+ throw new Error(`Assembled file has ${assembledStat.size}/${file.size} bytes`)
1037
+ }
1038
+
1039
+ const verificationStartedAt = Date.now()
1040
+ const capabilities = await this.getGalleryCapabilities()
1041
+ if (capabilities?.sha256) {
1042
+ const remoteHash = file.sha256 ? {sha256: file.sha256, etag: file.etag} : await this.getV3FileHash(file.name)
1043
+ if (file.etag && remoteHash.etag && file.etag !== remoteHash.etag) {
1044
+ throw new Error("Source generation changed before integrity verification")
1045
+ }
1046
+ const localHash = await RNFS.hash(assemblingPath, "sha256")
1047
+ if (localHash.toLowerCase() !== remoteHash.sha256.toLowerCase()) {
1048
+ await RNFS.moveFile(assemblingPath, `${assemblingPath}.invalid.${Date.now()}`).catch(() => {})
1049
+ throw new Error(`SHA-256 mismatch for ${file.name}`)
1050
+ }
1051
+ galleryTransferLedger.markFileHash(captureId, file.name, localHash.toLowerCase())
1052
+ }
1053
+
1054
+ await RNFS.moveFile(assemblingPath, localFilePath)
1055
+ for (let index = 0; index < segmentCount; index++) await RNFS.unlink(partPath(index)).catch(() => {})
1056
+ console.log("[ASG Camera API] Gallery file committed", {
1057
+ captureId,
1058
+ file: file.name,
1059
+ bytes: file.size,
1060
+ segments: segmentCount,
1061
+ networkDurationMs,
1062
+ networkMbps: networkDurationMs > 0 ? ((networkBytes * 8) / networkDurationMs / 1000).toFixed(2) : "0.00",
1063
+ verificationDurationMs: Date.now() - verificationStartedAt,
1064
+ totalDurationMs: Date.now() - transferStartedAt,
1065
+ resumedSegments: resumedSegmentCount,
1066
+ })
1067
+ onProgress?.(file.size)
1068
+ }
1069
+
1070
+ async downloadCapture(
1071
+ capture: CaptureGroup,
1072
+ onProgress?: (bytesDownloaded: number, totalBytes: number) => void,
1073
+ abortSignal?: AbortSignal,
1074
+ ): Promise<{captureDir: string; primaryPath: string; bracketPaths: string[]; sidecarPath?: string}> {
1075
+ if (capture.files.some((file) => !file.size || file.size <= 0)) {
1076
+ // Preserve support for malformed manifests from pre-v2 development firmware.
1077
+ return this.downloadCaptureLegacyUnsafe(capture, onProgress, abortSignal)
1078
+ }
1079
+ const captureDir = localStorageService.getPhotoFilePath(capture.capture_id)
1080
+ if (!(await RNFS.exists(captureDir))) await RNFS.mkdir(captureDir)
1081
+ const capabilities = await this.getGalleryCapabilities()
1082
+ galleryTransferLedger.ensureCapture(capture, capabilities?.api_version || 2)
1083
+ galleryTransferLedger.transition(capture.capture_id, "TRANSFERRING")
1084
+
1085
+ const sortedFiles = [...capture.files].sort((a, b) => {
1086
+ const order = {bracket: 0, primary: 1, sidecar: 2}
1087
+ return (order[a.role] ?? 1) - (order[b.role] ?? 1)
1088
+ })
1089
+ let completedCaptureBytes = 0
1090
+ let primaryPath = ""
1091
+ const bracketPaths: string[] = []
1092
+ let sidecarPath: string | undefined
1093
+
1094
+ try {
1095
+ for (const file of sortedFiles) {
1096
+ const leafName = file.name.includes("/") ? file.name.split("/").pop()! : file.name
1097
+ const localFilePath = `${captureDir}/${leafName}`
1098
+ await this.downloadResumableCaptureFile(
1099
+ capture.capture_id,
1100
+ file,
1101
+ localFilePath,
1102
+ (fileBytes) => onProgress?.(completedCaptureBytes + fileBytes, capture.total_size),
1103
+ abortSignal,
1104
+ )
1105
+
1106
+ const isVideo = !!file.name.match(/\.(mp4|mov|avi|webm|mkv)$/i)
1107
+ const mediaKind = file.role === "sidecar" ? "unknown" : isVideo ? "video" : "photo"
1108
+ try {
1109
+ await validateDownloadedMediaFile({
1110
+ path: localFilePath,
1111
+ name: file.name,
1112
+ expectedSize: file.size,
1113
+ mediaKind,
1114
+ })
1115
+ } catch (validationError: any) {
1116
+ reportInvalidGalleryMedia({
1117
+ name: file.name,
1118
+ path: localFilePath,
1119
+ mediaKind,
1120
+ stage: "download_capture_validation",
1121
+ reason: validationError?.message || String(validationError),
1122
+ expectedSize: file.size,
1123
+ captureId: capture.capture_id,
1124
+ duration: capture.duration,
1125
+ })
1126
+ await RNFS.moveFile(localFilePath, `${localFilePath}.invalid.${Date.now()}`).catch(() => {})
1127
+ throw validationError
1128
+ }
1129
+
1130
+ completedCaptureBytes += file.size
1131
+ if (file.role === "primary") primaryPath = localFilePath
1132
+ else if (file.role === "bracket") bracketPaths.push(localFilePath)
1133
+ else if (file.role === "sidecar") sidecarPath = localFilePath
1134
+ }
1135
+ if (!primaryPath && sortedFiles.length > 0) {
1136
+ const leafName = sortedFiles[0].name.includes("/") ? sortedFiles[0].name.split("/").pop()! : sortedFiles[0].name
1137
+ primaryPath = `${captureDir}/${leafName}`
1138
+ }
1139
+ galleryTransferLedger.transition(capture.capture_id, "VERIFIED")
1140
+ onProgress?.(capture.total_size, capture.total_size)
1141
+ return {captureDir, primaryPath, bracketPaths, sidecarPath}
1142
+ } catch (error) {
1143
+ galleryTransferLedger.recordFailure(capture.capture_id, error)
1144
+ throw error
1145
+ }
1146
+ }
1147
+
1148
+ /** Previous direct-to-final implementation kept for binary-source compatibility only. */
1149
+ private async downloadCaptureLegacyUnsafe(
1150
+ capture: CaptureGroup,
1151
+ onProgress?: (bytesDownloaded: number, totalBytes: number) => void,
1152
+ abortSignal?: AbortSignal,
1153
+ ): Promise<{captureDir: string; primaryPath: string; bracketPaths: string[]; sidecarPath?: string}> {
1154
+ const captureDir = localStorageService.getPhotoFilePath(capture.capture_id)
1155
+ console.log(
1156
+ `[ASG Camera API] downloadCapture: ${capture.capture_id} (${capture.files.length} files) -> ${captureDir}`,
1157
+ )
1158
+
1159
+ // Ensure capture directory exists
1160
+ const dirExists = await RNFS.exists(captureDir)
1161
+ if (!dirExists) {
1162
+ await RNFS.mkdir(captureDir)
1163
+ console.log(`[ASG Camera API] downloadCapture: created dir ${captureDir}`)
1164
+ }
1165
+
1166
+ let primaryPath = ""
1167
+ const bracketPaths: string[] = []
1168
+ let sidecarPath: string | undefined
1169
+
1170
+ let totalBytesDownloaded = 0
1171
+ const totalBytes = capture.total_size
1172
+
1173
+ // Sort files: brackets first, then primary, then sidecar
1174
+ // This ensures brackets are available before merge runs on the primary
1175
+ const sortedFiles = [...capture.files].sort((a, b) => {
1176
+ const order = {bracket: 0, primary: 1, sidecar: 2}
1177
+ return (order[a.role] ?? 1) - (order[b.role] ?? 1)
1178
+ })
1179
+
1180
+ for (const file of sortedFiles) {
1181
+ // Derive local filename: use leaf of path if folder-based, otherwise full name
1182
+ const leafName = file.name.includes("/") ? file.name.split("/").pop()! : file.name
1183
+ const localFilePath = `${captureDir}/${leafName}`
1184
+
1185
+ const isVideo = file.name.match(/\.(mp4|mov|avi|webm|mkv)$/i)
1186
+ const downloadEndpoint = isVideo ? "download" : "photo"
1187
+ const downloadUrl = `${this.baseUrl}/api/${downloadEndpoint}?file=${encodeURIComponent(file.name)}`
1188
+
1189
+ console.log(`[ASG Camera API] downloadCapture: downloading ${file.name} (${file.role}) -> ${localFilePath}`)
1190
+
1191
+ try {
1192
+ // Throttle progress: only fire when bytesWritten changes meaningfully
1193
+ let lastReportedBytes = -1
1194
+ const {jobId, promise: dlPromise} = localNetworkTransport.downloadFile({
1195
+ fromUrl: downloadUrl,
1196
+ toFile: localFilePath,
1197
+ headers: {
1198
+ "Accept": "*/*",
1199
+ "User-Agent": "MentraOS-Mobile/1.0",
1200
+ },
1201
+ connectionTimeout: 300000,
1202
+ readTimeout: 300000,
1203
+ backgroundTimeout: 600000,
1204
+ progressDivider: 5,
1205
+ progressInterval: 250,
1206
+ progress: (res: {bytesWritten: number}) => {
1207
+ const currentBytes = totalBytesDownloaded + (res.bytesWritten || 0)
1208
+ if (onProgress && currentBytes !== lastReportedBytes) {
1209
+ lastReportedBytes = currentBytes
1210
+ onProgress(currentBytes, totalBytes)
1211
+ }
1212
+ },
1213
+ })
1214
+
1215
+ // Wire up abort signal via polling (safe for all Hermes versions)
1216
+ let abortPollTimer: number | undefined
1217
+ if (abortSignal) {
1218
+ if (abortSignal.aborted) {
1219
+ localNetworkTransport.stopDownload(jobId)
1220
+ throw new Error("Sync cancelled")
1221
+ }
1222
+ abortPollTimer = BgTimer.setInterval(() => {
1223
+ if (abortSignal.aborted) {
1224
+ localNetworkTransport.stopDownload(jobId)
1225
+ }
1226
+ }, 500)
1227
+ }
1228
+
1229
+ let downloadResult
1230
+ try {
1231
+ downloadResult = await dlPromise
1232
+ } finally {
1233
+ if (abortPollTimer !== undefined) BgTimer.clearInterval(abortPollTimer)
1234
+ }
1235
+
1236
+ if (downloadResult.statusCode !== 200) {
1237
+ await RNFS.unlink(localFilePath).catch(() => {})
1238
+ throw new Error(`HTTP ${downloadResult.statusCode}`)
1239
+ }
1240
+
1241
+ // Check if aborted after completion
1242
+ if (abortSignal?.aborted) {
1243
+ await RNFS.unlink(localFilePath).catch(() => {})
1244
+ throw new Error("Sync cancelled")
1245
+ }
1246
+
1247
+ const mediaKind = file.role === "sidecar" ? "unknown" : isVideo ? "video" : "photo"
1248
+ try {
1249
+ await validateDownloadedMediaFile({
1250
+ path: localFilePath,
1251
+ name: file.name,
1252
+ expectedSize: file.size,
1253
+ mediaKind,
1254
+ })
1255
+ } catch (validationErr: any) {
1256
+ const reason = validationErr?.message || validationErr?.toString?.() || JSON.stringify(validationErr)
1257
+ reportInvalidGalleryMedia({
1258
+ name: file.name,
1259
+ path: localFilePath,
1260
+ mediaKind,
1261
+ stage: "download_capture_validation",
1262
+ reason,
1263
+ expectedSize: file.size,
1264
+ captureId: capture.capture_id,
1265
+ duration: capture.duration,
1266
+ })
1267
+ await RNFS.unlink(localFilePath).catch(() => {})
1268
+ throw validationErr
1269
+ }
1270
+
1271
+ console.log(`[ASG Camera API] downloadCapture: completed ${file.name} (${file.size} bytes)`)
1272
+ } catch (dlErr: any) {
1273
+ // S2: Clean up partial file on failure
1274
+ await RNFS.unlink(localFilePath).catch(() => {})
1275
+ const errMsg = dlErr?.message || dlErr?.toString?.() || JSON.stringify(dlErr)
1276
+ console.error(`[ASG Camera API] downloadCapture: FAILED ${file.name}: ${errMsg}`)
1277
+ throw new Error(`Failed to download ${file.name}: ${errMsg}`)
1278
+ }
1279
+
1280
+ totalBytesDownloaded += file.size
1281
+
1282
+ if (file.role === "primary") {
1283
+ primaryPath = localFilePath
1284
+ } else if (file.role === "bracket") {
1285
+ bracketPaths.push(localFilePath)
1286
+ } else if (file.role === "sidecar") {
1287
+ sidecarPath = localFilePath
1288
+ }
1289
+ }
1290
+
1291
+ // C3: If no file was marked as primary, fall back to first downloaded file
1292
+ if (primaryPath === "" && sortedFiles.length > 0) {
1293
+ const leafName = sortedFiles[0].name.includes("/") ? sortedFiles[0].name.split("/").pop()! : sortedFiles[0].name
1294
+ primaryPath = `${captureDir}/${leafName}`
1295
+ console.warn(`[ASG Camera API] downloadCapture: No primary file found, falling back to ${primaryPath}`)
1296
+ }
1297
+
1298
+ // Report final progress
1299
+ if (onProgress) {
1300
+ onProgress(totalBytes, totalBytes)
1301
+ }
1302
+
1303
+ return {captureDir, primaryPath, bracketPaths, sidecarPath}
1304
+ }
1305
+
1306
+ /**
1307
+ * Delete files from server
1308
+ */
1309
+ async deleteFilesFromServer(fileNames: string[]): Promise<{
1310
+ deleted: string[]
1311
+ failed: string[]
1312
+ }> {
1313
+ if (fileNames.length === 0) {
1314
+ return {deleted: [], failed: []}
1315
+ }
1316
+
1317
+ try {
1318
+ const response = await this.makeRequest(
1319
+ "/api/delete-files",
1320
+ {
1321
+ method: "POST",
1322
+ body: JSON.stringify({files: fileNames}),
1323
+ },
1324
+ 5,
1325
+ 0,
1326
+ )
1327
+
1328
+ // Parse the response format from the ASG server
1329
+ const responseData = response as any
1330
+ if (responseData.data && responseData.data.results) {
1331
+ const deleted: string[] = []
1332
+ const failed: string[] = []
1333
+
1334
+ for (const result of responseData.data.results) {
1335
+ if (result.success) {
1336
+ deleted.push(result.file)
1337
+ } else {
1338
+ failed.push(result.file)
1339
+ }
1340
+ }
1341
+
1342
+ console.log(`[ASG Camera API] Delete results: ${deleted.length} deleted, ${failed.length} failed`)
1343
+ return {deleted, failed}
1344
+ }
1345
+
1346
+ return response as {
1347
+ deleted: string[]
1348
+ failed: string[]
1349
+ }
1350
+ } catch (error) {
1351
+ console.error("Failed to delete files from server:", error)
1352
+ return {deleted: [], failed: fileNames}
1353
+ }
1354
+ }
1355
+
1356
+ /**
1357
+ * Get sync status from server
1358
+ */
1359
+ async getSyncStatus(): Promise<{
1360
+ total_files: number
1361
+ total_size: number
1362
+ last_modified: number
1363
+ }> {
1364
+ const response = await this.makeRequest("/sync/status", {
1365
+ method: "GET",
1366
+ })
1367
+
1368
+ return response as {
1369
+ total_files: number
1370
+ total_size: number
1371
+ last_modified: number
1372
+ }
1373
+ }
1374
+
1375
+ /**
1376
+ * Download a file from the server and save to filesystem
1377
+ */
1378
+ async downloadFile(
1379
+ filename: string,
1380
+ includeThumbnail: boolean = false,
1381
+ onProgress?: (progress: number) => void,
1382
+ abortSignal?: AbortSignal,
1383
+ expectedSize?: number,
1384
+ ): Promise<{filePath: string; thumbnailPath?: string; mime_type: string}> {
1385
+ // The legacy public shape did not require a size. Keep that rare call compatible while all
1386
+ // sync manifests use the verified resumable path below.
1387
+ if (!expectedSize || expectedSize <= 0) {
1388
+ return this.downloadFileLegacyUnsafe(filename, includeThumbnail, onProgress, abortSignal, expectedSize)
1389
+ }
1390
+
1391
+ const localFilePath = localStorageService.getPhotoFilePath(filename)
1392
+ const parentDir = localFilePath.substring(0, localFilePath.lastIndexOf("/"))
1393
+ if (!(await RNFS.exists(parentDir))) await RNFS.mkdir(parentDir)
1394
+ const lowerName = filename.toLowerCase()
1395
+ const isVideo = /\.(mp4|mov|avi|webm|mkv)$/.test(lowerName)
1396
+ const captureId = filename.includes("/")
1397
+ ? filename.substring(0, filename.indexOf("/"))
1398
+ : filename.replace(/\.imu\.json$/i, "").replace(/\.[^.]+$/, "")
1399
+ const capture: CaptureGroup = {
1400
+ capture_id: captureId,
1401
+ type: isVideo ? "video" : "photo",
1402
+ timestamp: Date.now(),
1403
+ total_size: expectedSize,
1404
+ files: [{name: filename, size: expectedSize, role: "primary"}],
1405
+ }
1406
+ const capabilities = await this.getGalleryCapabilities()
1407
+ galleryTransferLedger.ensureCapture(capture, capabilities?.api_version || 1)
1408
+ galleryTransferLedger.transition(captureId, "TRANSFERRING")
1409
+ try {
1410
+ await this.downloadResumableCaptureFile(
1411
+ captureId,
1412
+ capture.files[0],
1413
+ localFilePath,
1414
+ (bytes) => onProgress?.(Math.min(100, Math.round((bytes / expectedSize) * 100))),
1415
+ abortSignal,
1416
+ )
1417
+ await validateDownloadedMediaFile({
1418
+ path: localFilePath,
1419
+ name: filename,
1420
+ expectedSize,
1421
+ mediaKind: isVideo ? "video" : "photo",
1422
+ })
1423
+ galleryTransferLedger.transition(captureId, "VERIFIED")
1424
+ return {
1425
+ filePath: localFilePath,
1426
+ mime_type: isVideo
1427
+ ? "video/mp4"
1428
+ : lowerName.endsWith(".png")
1429
+ ? "image/png"
1430
+ : lowerName.endsWith(".avif")
1431
+ ? "image/avif"
1432
+ : "image/jpeg",
1433
+ }
1434
+ } catch (error) {
1435
+ galleryTransferLedger.recordFailure(captureId, error)
1436
+ throw error
1437
+ }
1438
+ }
1439
+
1440
+ private async downloadFileLegacyUnsafe(
1441
+ filename: string,
1442
+ includeThumbnail: boolean = false,
1443
+ onProgress?: (progress: number) => void,
1444
+ abortSignal?: AbortSignal,
1445
+ expectedSize?: number,
1446
+ ): Promise<{
1447
+ filePath: string
1448
+ thumbnailPath?: string
1449
+ mime_type: string
1450
+ }> {
1451
+ console.log(`[ASG Camera API] Downloading file: ${filename}`)
1452
+
1453
+ try {
1454
+ // Get the local file path where we'll save this
1455
+ const localFilePath = localStorageService.getPhotoFilePath(filename)
1456
+ const localThumbnailPath = includeThumbnail ? localStorageService.getThumbnailFilePath(filename) : undefined
1457
+
1458
+ // Ensure parent directory exists (for folder-based capture paths like IMG_xxx/base.jpg)
1459
+ if (filename.includes("/")) {
1460
+ const parentDir = localFilePath.substring(0, localFilePath.lastIndexOf("/"))
1461
+ const parentExists = await RNFS.exists(parentDir)
1462
+ if (!parentExists) {
1463
+ await RNFS.mkdir(parentDir)
1464
+ }
1465
+ }
1466
+
1467
+ // Determine if this is a video file based on extension
1468
+ const isVideo = filename.match(/\.(mp4|mov|avi|webm|mkv)$/i)
1469
+
1470
+ // Use /api/download for videos (full file) and /api/photo for images
1471
+ const downloadEndpoint = isVideo ? "download" : "photo"
1472
+ const downloadUrl = `${this.baseUrl}/api/${downloadEndpoint}?file=${encodeURIComponent(filename)}`
1473
+
1474
+ // Download the file directly to filesystem
1475
+ console.log(`[ASG Camera API] Downloading ${isVideo ? "video" : "photo"} from: ${downloadUrl}`)
1476
+ console.log(`[ASG Camera API] Saving to: ${localFilePath}`)
1477
+
1478
+ // Track content length from begin callback for post-download validation
1479
+ let expectedContentLength = 0
1480
+ // Throttle progress: only call onProgress when percentage actually changes
1481
+ let lastReportedProgress = -1
1482
+
1483
+ const {jobId, promise: downloadPromise} = localNetworkTransport.downloadFile({
1484
+ fromUrl: downloadUrl,
1485
+ toFile: localFilePath,
1486
+ headers: {
1487
+ "Accept": "*/*",
1488
+ "User-Agent": "MentraOS-Mobile/1.0",
1489
+ },
1490
+ connectionTimeout: 300000, // 5 minutes for connection establishment
1491
+ readTimeout: 300000, // 5 minutes for data reading
1492
+ backgroundTimeout: 600000, // 10 minutes for background downloads (iOS)
1493
+ progressDivider: 5, // Fire progress every 5% to reduce event frequency
1494
+ progressInterval: 250, // Update progress every 250ms max
1495
+ begin: (res) => {
1496
+ expectedContentLength = res.contentLength || 0
1497
+ console.log(`[ASG Camera API] Download started for ${filename}, size: ${res.contentLength}`)
1498
+ },
1499
+ progress: (res) => {
1500
+ // Validate progress data to prevent negative percentages
1501
+ const contentLength = res.contentLength || 0
1502
+ const bytesWritten = res.bytesWritten || 0
1503
+
1504
+ let percentage = 0
1505
+ if (contentLength > 0 && bytesWritten >= 0) {
1506
+ percentage = Math.round((bytesWritten / contentLength) * 100)
1507
+ // Clamp percentage between 0 and 100
1508
+ percentage = Math.max(0, Math.min(100, percentage))
1509
+ }
1510
+
1511
+ // Only call onProgress when percentage actually changes (throttle)
1512
+ if (onProgress && percentage !== lastReportedProgress) {
1513
+ lastReportedProgress = percentage
1514
+ onProgress(percentage)
1515
+ }
1516
+
1517
+ // Log every 10%
1518
+ if (percentage % 10 === 0) {
1519
+ console.log(`[ASG Camera API] Download progress ${filename}: ${percentage}%`)
1520
+ }
1521
+ },
1522
+ })
1523
+
1524
+ // Wire up abort signal to stop download via polling (safe for all Hermes versions)
1525
+ let abortPollTimer: number | undefined
1526
+ if (abortSignal) {
1527
+ if (abortSignal.aborted) {
1528
+ localNetworkTransport.stopDownload(jobId)
1529
+ throw new Error("Sync cancelled")
1530
+ }
1531
+ abortPollTimer = BgTimer.setInterval(() => {
1532
+ if (abortSignal.aborted) {
1533
+ localNetworkTransport.stopDownload(jobId)
1534
+ }
1535
+ }, 500)
1536
+ }
1537
+
1538
+ let downloadResult
1539
+ try {
1540
+ downloadResult = await downloadPromise
1541
+ } finally {
1542
+ if (abortPollTimer !== undefined) BgTimer.clearInterval(abortPollTimer)
1543
+ }
1544
+
1545
+ if (downloadResult.statusCode !== 200) {
1546
+ // S2: Clean up partial file on HTTP error
1547
+ await RNFS.unlink(localFilePath).catch(() => {})
1548
+ throw new Error(`Failed to download ${filename}: HTTP ${downloadResult.statusCode}`)
1549
+ }
1550
+
1551
+ // Check if download was aborted after completion
1552
+ if (abortSignal?.aborted) {
1553
+ await RNFS.unlink(localFilePath).catch(() => {})
1554
+ throw new Error("Sync cancelled")
1555
+ }
1556
+
1557
+ // N1: Validate file size after download.
1558
+ // Use Content-Length from the HTTP response if available, otherwise fall back
1559
+ // to expectedSize from the sync response metadata. This catches truncated
1560
+ // downloads even when the server uses chunked transfer encoding (no Content-Length).
1561
+ const sizeToCheck =
1562
+ expectedContentLength > 0 ? expectedContentLength : expectedSize && expectedSize > 0 ? expectedSize : 0
1563
+
1564
+ try {
1565
+ await validateDownloadedMediaFile({
1566
+ path: localFilePath,
1567
+ name: filename,
1568
+ expectedSize: sizeToCheck,
1569
+ mediaKind: isVideo ? "video" : "photo",
1570
+ })
1571
+ } catch (validationErr: any) {
1572
+ const reason = validationErr?.message || validationErr?.toString?.() || JSON.stringify(validationErr)
1573
+ reportInvalidGalleryMedia({
1574
+ name: filename,
1575
+ path: localFilePath,
1576
+ mediaKind: isVideo ? "video" : "photo",
1577
+ stage: "download_validation",
1578
+ reason,
1579
+ expectedSize: sizeToCheck,
1580
+ })
1581
+ await RNFS.unlink(localFilePath).catch(() => {})
1582
+ throw validationErr
1583
+ }
1584
+
1585
+ console.log(`[ASG Camera API] Successfully downloaded ${filename} to filesystem`)
1586
+
1587
+ // Detect MIME type by checking file signature
1588
+ let mimeType = "application/octet-stream"
1589
+ try {
1590
+ // Read first 20 bytes to check file signature
1591
+ const firstBytes = await RNFS.read(localFilePath, 20, 0, "base64")
1592
+ const decodedBytes = atob(firstBytes)
1593
+
1594
+ // Check for AVIF signature
1595
+ if (decodedBytes.length > 11) {
1596
+ const ftypSignature = decodedBytes.substring(4, 12)
1597
+ if (ftypSignature === "ftypavif") {
1598
+ mimeType = "image/avif"
1599
+ console.log(`[ASG Camera API] Detected AVIF file: ${filename}`)
1600
+ } else if (decodedBytes.substring(0, 2) === "\xFF\xD8") {
1601
+ mimeType = "image/jpeg"
1602
+ } else if (decodedBytes.substring(0, 8) === "\x89PNG\r\n\x1a\n") {
1603
+ mimeType = "image/png"
1604
+ }
1605
+ }
1606
+
1607
+ // Also check by extension
1608
+ if (mimeType === "application/octet-stream") {
1609
+ if (filename.toLowerCase().endsWith(".jpg") || filename.toLowerCase().endsWith(".jpeg")) {
1610
+ mimeType = "image/jpeg"
1611
+ } else if (filename.toLowerCase().endsWith(".png")) {
1612
+ mimeType = "image/png"
1613
+ } else if (filename.toLowerCase().endsWith(".mp4")) {
1614
+ mimeType = "video/mp4"
1615
+ } else if (!filename.includes(".")) {
1616
+ // Files without extension are likely AVIF
1617
+ mimeType = "image/avif"
1618
+ }
1619
+ }
1620
+ } catch (e) {
1621
+ console.warn(`[ASG Camera API] Could not detect MIME type for ${filename}:`, e)
1622
+ }
1623
+
1624
+ // Download thumbnail if requested and it's a video
1625
+ let thumbnailPath: string | undefined
1626
+ if (includeThumbnail && filename.toLowerCase().match(/\.(mp4|mov|avi|mkv|webm)$/)) {
1627
+ try {
1628
+ console.log(`[ASG Camera API] Downloading thumbnail for ${filename}`)
1629
+ console.log(`[ASG Camera API] Using /api/photo endpoint for video thumbnail`)
1630
+
1631
+ // The server's /api/photo endpoint serves thumbnails for video files
1632
+ // It detects video files and automatically generates/serves thumbnails instead of the full video
1633
+ const thumbResult = await localNetworkTransport.downloadFile({
1634
+ fromUrl: `${this.baseUrl}/api/photo?file=${encodeURIComponent(filename)}`,
1635
+ toFile: localThumbnailPath as string,
1636
+ headers: {
1637
+ "Accept": "image/*",
1638
+ "User-Agent": "MentraOS-Mobile/1.0",
1639
+ },
1640
+ connectionTimeout: 60000, // 1 minute for thumbnails (smaller files)
1641
+ readTimeout: 60000, // 1 minute for thumbnails
1642
+ progressDivider: 1, // Get all progress updates for thumbnails too
1643
+ begin: (res) => {
1644
+ console.log(`[ASG Camera API] Thumbnail download started for ${filename}, size: ${res.contentLength}`)
1645
+ },
1646
+ progress: (res) => {
1647
+ const percentage = Math.round((res.bytesWritten / res.contentLength) * 100)
1648
+ if (percentage % 25 === 0) {
1649
+ console.log(`[ASG Camera API] Thumbnail download progress ${filename}: ${percentage}%`)
1650
+ }
1651
+ },
1652
+ }).promise
1653
+
1654
+ console.log(
1655
+ `[ASG Camera API] Thumbnail download result for ${filename}: status=${thumbResult.statusCode}, bytesWritten=${thumbResult.bytesWritten}`,
1656
+ )
1657
+
1658
+ if (thumbResult.statusCode === 200) {
1659
+ thumbnailPath = localThumbnailPath as string
1660
+ console.log(`[ASG Camera API] Successfully downloaded thumbnail to: ${thumbnailPath}`)
1661
+
1662
+ // Verify the file exists
1663
+ const exists = await RNFS.exists(thumbnailPath)
1664
+ console.log(`[ASG Camera API] Thumbnail file exists: ${exists}`)
1665
+ } else {
1666
+ console.warn(`[ASG Camera API] Thumbnail download failed with status: ${thumbResult.statusCode}`)
1667
+ }
1668
+ } catch (error) {
1669
+ console.warn(`[ASG Camera API] Failed to download thumbnail for ${filename}:`, error)
1670
+ }
1671
+ } else {
1672
+ console.log(
1673
+ `[ASG Camera API] Skipping thumbnail download - includeThumbnail: ${includeThumbnail}, filename: ${filename}, is video extension: ${
1674
+ filename.toLowerCase().match(/\.(mp4|mov|avi|mkv|webm)$/) ? "yes" : "no"
1675
+ }`,
1676
+ )
1677
+ }
1678
+
1679
+ return {
1680
+ filePath: localFilePath,
1681
+ thumbnailPath: thumbnailPath,
1682
+ mime_type: mimeType,
1683
+ }
1684
+ } catch (error) {
1685
+ console.error(`[ASG Camera API] Error downloading file ${filename}:`, error)
1686
+ // S2: Clean up partial file on any failure
1687
+ const localFilePath = localStorageService.getPhotoFilePath(filename)
1688
+ await RNFS.unlink(localFilePath).catch(() => {})
1689
+ throw error
1690
+ }
1691
+ }
1692
+ }
1693
+
1694
+ // Export a default instance - will be initialized with proper IP when used
1695
+ export const asgCameraApi = new AsgCameraApiClient()