@mentra/engine 0.1.0-dev.0 → 3.1.0-dev.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -18
- package/build/bluetooth-sdk/debug.d.ts +2 -0
- package/build/bluetooth-sdk/debug.d.ts.map +1 -0
- package/build/bluetooth-sdk/debug.js +2 -0
- package/build/bluetooth-sdk/debug.js.map +1 -0
- package/build/bluetooth-sdk/index.d.ts +3 -0
- package/build/bluetooth-sdk/index.d.ts.map +1 -0
- package/build/bluetooth-sdk/index.js +3 -0
- package/build/bluetooth-sdk/index.js.map +1 -0
- package/build/bluetooth-sdk/photo-receiver.d.ts +3 -0
- package/build/bluetooth-sdk/photo-receiver.d.ts.map +1 -0
- package/build/bluetooth-sdk/photo-receiver.js +3 -0
- package/build/bluetooth-sdk/photo-receiver.js.map +1 -0
- package/build/bluetooth-sdk/react.d.ts +2 -0
- package/build/bluetooth-sdk/react.d.ts.map +1 -0
- package/build/bluetooth-sdk/react.js +2 -0
- package/build/bluetooth-sdk/react.js.map +1 -0
- package/build/bluetooth-sdk/types.d.ts +2 -0
- package/build/bluetooth-sdk/types.d.ts.map +1 -0
- package/build/bluetooth-sdk/types.js +2 -0
- package/build/bluetooth-sdk/types.js.map +1 -0
- package/build/engine.d.ts +24 -16
- package/build/engine.d.ts.map +1 -1
- package/build/engine.js +9 -4
- package/build/engine.js.map +1 -1
- package/build/facades/dev.d.ts +3 -2
- package/build/facades/dev.d.ts.map +1 -1
- package/build/facades/dev.js +18 -12
- package/build/facades/dev.js.map +1 -1
- package/build/facades/display.d.ts +1 -1
- package/build/facades/display.d.ts.map +1 -1
- package/build/facades/display.js +1 -1
- package/build/facades/display.js.map +1 -1
- package/build/facades/gallery.js +1 -1
- package/build/facades/gallery.js.map +1 -1
- package/build/facades/glasses.d.ts +4 -13
- package/build/facades/glasses.d.ts.map +1 -1
- package/build/facades/glasses.js +21 -6
- package/build/facades/glasses.js.map +1 -1
- package/build/facades/glassesSettings.d.ts.map +1 -1
- package/build/facades/glassesSettings.js +1 -0
- package/build/facades/glassesSettings.js.map +1 -1
- package/build/facades/miniapps.d.ts +4 -0
- package/build/facades/miniapps.d.ts.map +1 -1
- package/build/facades/miniapps.js +5 -0
- package/build/facades/miniapps.js.map +1 -1
- package/build/facades/ota.d.ts +14 -16
- package/build/facades/ota.d.ts.map +1 -1
- package/build/facades/ota.js +22 -7
- package/build/facades/ota.js.map +1 -1
- package/build/facades/pairing.d.ts +8 -5
- package/build/facades/pairing.d.ts.map +1 -1
- package/build/facades/pairing.js +12 -10
- package/build/facades/pairing.js.map +1 -1
- package/build/facades/permissions.d.ts +10 -0
- package/build/facades/permissions.d.ts.map +1 -1
- package/build/facades/permissions.js +10 -0
- package/build/facades/permissions.js.map +1 -1
- package/build/facades/phoneNotifications.d.ts +0 -4
- package/build/facades/phoneNotifications.d.ts.map +1 -1
- package/build/facades/phoneNotifications.js +4 -14
- package/build/facades/phoneNotifications.js.map +1 -1
- package/build/facades/speech.d.ts +7 -6
- package/build/facades/speech.d.ts.map +1 -1
- package/build/facades/speech.js +1 -1
- package/build/facades/speech.js.map +1 -1
- package/build/generated/releaseMetadata.d.ts +12 -0
- package/build/generated/releaseMetadata.d.ts.map +1 -0
- package/build/generated/releaseMetadata.js +10 -0
- package/build/generated/releaseMetadata.js.map +1 -0
- package/build/index.d.ts +10 -8
- package/build/index.d.ts.map +1 -1
- package/build/index.js +6 -5
- package/build/index.js.map +1 -1
- package/build/react/MentraLiveOtaFlow.d.ts +38 -0
- package/build/react/MentraLiveOtaFlow.d.ts.map +1 -0
- package/build/react/MentraLiveOtaFlow.js +528 -0
- package/build/react/MentraLiveOtaFlow.js.map +1 -0
- package/build/react/index.d.ts +2 -0
- package/build/react/index.d.ts.map +1 -0
- package/build/react/index.js +2 -0
- package/build/react/index.js.map +1 -0
- package/build/react/useEngineSnapshot.d.ts +2 -0
- package/build/react/useEngineSnapshot.d.ts.map +1 -0
- package/build/react/useEngineSnapshot.js +14 -0
- package/build/react/useEngineSnapshot.js.map +1 -0
- package/build/runtime/bootstrap.d.ts +24 -1
- package/build/runtime/bootstrap.d.ts.map +1 -1
- package/build/runtime/bootstrap.js +17 -0
- package/build/runtime/bootstrap.js.map +1 -1
- package/build/runtime/scanQrSeam.d.ts +7 -0
- package/build/runtime/scanQrSeam.d.ts.map +1 -0
- package/build/runtime/scanQrSeam.js +16 -0
- package/build/runtime/scanQrSeam.js.map +1 -0
- package/build/services/AppRegistry.d.ts +41 -57
- package/build/services/AppRegistry.d.ts.map +1 -1
- package/build/services/AppRegistry.js +208 -144
- package/build/services/AppRegistry.js.map +1 -1
- package/build/services/AudioCloudUplink.d.ts +12 -0
- package/build/services/AudioCloudUplink.d.ts.map +1 -1
- package/build/services/AudioCloudUplink.js +22 -1
- package/build/services/AudioCloudUplink.js.map +1 -1
- package/build/services/AudioPlaybackService.d.ts +41 -2
- package/build/services/AudioPlaybackService.d.ts.map +1 -1
- package/build/services/AudioPlaybackService.js +250 -33
- package/build/services/AudioPlaybackService.js.map +1 -1
- package/build/services/BlobStore.d.ts +2 -1
- package/build/services/BlobStore.d.ts.map +1 -1
- package/build/services/BlobStore.js +17 -9
- package/build/services/BlobStore.js.map +1 -1
- package/build/services/CloudAudioSubscriptionSync.d.ts.map +1 -1
- package/build/services/CloudAudioSubscriptionSync.js +13 -1
- package/build/services/CloudAudioSubscriptionSync.js.map +1 -1
- package/build/services/CloudClientService.d.ts +15 -0
- package/build/services/CloudClientService.d.ts.map +1 -1
- package/build/services/CloudClientService.js +93 -1
- package/build/services/CloudClientService.js.map +1 -1
- package/build/services/DeviceEventRouter.js +5 -5
- package/build/services/DeviceEventRouter.js.map +1 -1
- package/build/services/ForegroundLocationPermission.d.ts +13 -0
- package/build/services/ForegroundLocationPermission.d.ts.map +1 -0
- package/build/services/ForegroundLocationPermission.js +12 -0
- package/build/services/ForegroundLocationPermission.js.map +1 -0
- package/build/services/GlassesSettingsSync.d.ts.map +1 -1
- package/build/services/GlassesSettingsSync.js +9 -3
- package/build/services/GlassesSettingsSync.js.map +1 -1
- package/build/services/GlassesStatusProjection.d.ts +2 -1
- package/build/services/GlassesStatusProjection.d.ts.map +1 -1
- package/build/services/GlassesStatusProjection.js +15 -8
- package/build/services/GlassesStatusProjection.js.map +1 -1
- package/build/services/HotspotOtaTransport.d.ts +29 -0
- package/build/services/HotspotOtaTransport.d.ts.map +1 -0
- package/build/services/HotspotOtaTransport.js +146 -0
- package/build/services/HotspotOtaTransport.js.map +1 -0
- package/build/services/HotspotShutdown.d.ts +17 -0
- package/build/services/HotspotShutdown.d.ts.map +1 -0
- package/build/services/HotspotShutdown.js +29 -0
- package/build/services/HotspotShutdown.js.map +1 -0
- package/build/services/LocalDisplayManager.d.ts +15 -0
- package/build/services/LocalDisplayManager.d.ts.map +1 -1
- package/build/services/LocalDisplayManager.js +106 -20
- package/build/services/LocalDisplayManager.js.map +1 -1
- package/build/services/LocalMiniappRuntime.d.ts +74 -2
- package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
- package/build/services/LocalMiniappRuntime.js +785 -291
- package/build/services/LocalMiniappRuntime.js.map +1 -1
- package/build/services/LocalMiniappStorage.d.ts +32 -0
- package/build/services/LocalMiniappStorage.d.ts.map +1 -0
- package/build/services/LocalMiniappStorage.js +66 -0
- package/build/services/LocalMiniappStorage.js.map +1 -0
- package/build/services/LocalMiniappUserIdentity.d.ts +20 -0
- package/build/services/LocalMiniappUserIdentity.d.ts.map +1 -0
- package/build/services/LocalMiniappUserIdentity.js +43 -0
- package/build/services/LocalMiniappUserIdentity.js.map +1 -0
- package/build/services/LocalSttFallbackCoordinator.d.ts +9 -6
- package/build/services/LocalSttFallbackCoordinator.d.ts.map +1 -1
- package/build/services/LocalSttFallbackCoordinator.js +18 -12
- package/build/services/LocalSttFallbackCoordinator.js.map +1 -1
- package/build/services/MicStateCoordinator.d.ts +42 -1
- package/build/services/MicStateCoordinator.d.ts.map +1 -1
- package/build/services/MicStateCoordinator.js +141 -3
- package/build/services/MicStateCoordinator.js.map +1 -1
- package/build/services/MiniappLauncher.d.ts.map +1 -1
- package/build/services/MiniappLauncher.js +23 -3
- package/build/services/MiniappLauncher.js.map +1 -1
- package/build/services/MiniappLiveness.d.ts +12 -0
- package/build/services/MiniappLiveness.d.ts.map +1 -0
- package/build/services/MiniappLiveness.js +16 -0
- package/build/services/MiniappLiveness.js.map +1 -0
- package/build/services/NativeHttpResponse.d.ts +3 -0
- package/build/services/NativeHttpResponse.d.ts.map +1 -0
- package/build/services/NativeHttpResponse.js +6 -0
- package/build/services/NativeHttpResponse.js.map +1 -0
- package/build/services/OtaArtifactDownloader.d.ts +65 -0
- package/build/services/OtaArtifactDownloader.d.ts.map +1 -0
- package/build/services/OtaArtifactDownloader.js +226 -0
- package/build/services/OtaArtifactDownloader.js.map +1 -0
- package/build/services/OtaAutoChain.d.ts +34 -0
- package/build/services/OtaAutoChain.d.ts.map +1 -0
- package/build/services/OtaAutoChain.js +83 -0
- package/build/services/OtaAutoChain.js.map +1 -0
- package/build/services/OtaErrorMapping.d.ts +18 -0
- package/build/services/OtaErrorMapping.d.ts.map +1 -0
- package/build/services/OtaErrorMapping.js +63 -0
- package/build/services/OtaErrorMapping.js.map +1 -0
- package/build/services/OtaInstallCoordinator.d.ts +95 -15
- package/build/services/OtaInstallCoordinator.d.ts.map +1 -1
- package/build/services/OtaInstallCoordinator.js +577 -130
- package/build/services/OtaInstallCoordinator.js.map +1 -1
- package/build/services/OtaService.d.ts.map +1 -1
- package/build/services/OtaService.js +8 -1
- package/build/services/OtaService.js.map +1 -1
- package/build/services/OtaUpdateCheckService.d.ts +39 -4
- package/build/services/OtaUpdateCheckService.d.ts.map +1 -1
- package/build/services/OtaUpdateCheckService.js +138 -22
- package/build/services/OtaUpdateCheckService.js.map +1 -1
- package/build/services/PhoneCalendarHelpers.d.ts +32 -0
- package/build/services/PhoneCalendarHelpers.d.ts.map +1 -0
- package/build/services/PhoneCalendarHelpers.js +157 -0
- package/build/services/PhoneCalendarHelpers.js.map +1 -0
- package/build/services/PhoneCalendarService.d.ts +5 -0
- package/build/services/PhoneCalendarService.d.ts.map +1 -0
- package/build/services/PhoneCalendarService.js +45 -0
- package/build/services/PhoneCalendarService.js.map +1 -0
- package/build/services/PhoneCameraFovCoordinator.d.ts +30 -0
- package/build/services/PhoneCameraFovCoordinator.d.ts.map +1 -0
- package/build/services/PhoneCameraFovCoordinator.js +194 -0
- package/build/services/PhoneCameraFovCoordinator.js.map +1 -0
- package/build/services/PhoneNotificationsSync.d.ts.map +1 -1
- package/build/services/PhoneNotificationsSync.js +12 -10
- package/build/services/PhoneNotificationsSync.js.map +1 -1
- package/build/services/PhonePhotoCoordinator.d.ts +26 -2
- package/build/services/PhonePhotoCoordinator.d.ts.map +1 -1
- package/build/services/PhonePhotoCoordinator.js +79 -9
- package/build/services/PhonePhotoCoordinator.js.map +1 -1
- package/build/services/PhoneStreamCoordinator.d.ts +8 -1
- package/build/services/PhoneStreamCoordinator.d.ts.map +1 -1
- package/build/services/PhoneStreamCoordinator.js +125 -13
- package/build/services/PhoneStreamCoordinator.js.map +1 -1
- package/build/services/PhoneVideoCoordinator.d.ts +2 -0
- package/build/services/PhoneVideoCoordinator.d.ts.map +1 -1
- package/build/services/PhoneVideoCoordinator.js +10 -0
- package/build/services/PhoneVideoCoordinator.js.map +1 -1
- package/build/services/SceneRenderer.d.ts.map +1 -1
- package/build/services/SceneRenderer.js +15 -0
- package/build/services/SceneRenderer.js.map +1 -1
- package/build/services/SentenceTtsPipeline.d.ts +31 -0
- package/build/services/SentenceTtsPipeline.d.ts.map +1 -0
- package/build/services/SentenceTtsPipeline.js +67 -0
- package/build/services/SentenceTtsPipeline.js.map +1 -0
- package/build/services/SupportProfileSync.d.ts +22 -0
- package/build/services/SupportProfileSync.d.ts.map +1 -0
- package/build/services/SupportProfileSync.js +213 -0
- package/build/services/SupportProfileSync.js.map +1 -0
- package/build/services/TranscriptionRouting.d.ts +12 -0
- package/build/services/TranscriptionRouting.d.ts.map +1 -0
- package/build/services/TranscriptionRouting.js +24 -0
- package/build/services/TranscriptionRouting.js.map +1 -0
- package/build/services/TtsTextSanitizer.d.ts +5 -0
- package/build/services/TtsTextSanitizer.d.ts.map +1 -0
- package/build/services/TtsTextSanitizer.js +61 -0
- package/build/services/TtsTextSanitizer.js.map +1 -0
- package/build/services/WifiAdbRequest.d.ts +17 -0
- package/build/services/WifiAdbRequest.d.ts.map +1 -0
- package/build/services/WifiAdbRequest.js +35 -0
- package/build/services/WifiAdbRequest.js.map +1 -0
- package/build/services/asg/asgCameraApi.d.ts +43 -0
- package/build/services/asg/asgCameraApi.d.ts.map +1 -1
- package/build/services/asg/asgCameraApi.js +509 -26
- package/build/services/asg/asgCameraApi.js.map +1 -1
- package/build/services/asg/cameraRollExportCoordinator.d.ts +60 -0
- package/build/services/asg/cameraRollExportCoordinator.d.ts.map +1 -0
- package/build/services/asg/cameraRollExportCoordinator.js +618 -0
- package/build/services/asg/cameraRollExportCoordinator.js.map +1 -0
- package/build/services/asg/cameraRollExportLedger.d.ts +44 -0
- package/build/services/asg/cameraRollExportLedger.d.ts.map +1 -0
- package/build/services/asg/cameraRollExportLedger.js +78 -0
- package/build/services/asg/cameraRollExportLedger.js.map +1 -0
- package/build/services/asg/galleryNotices.d.ts +1 -1
- package/build/services/asg/galleryNotices.d.ts.map +1 -1
- package/build/services/asg/galleryNotices.js.map +1 -1
- package/build/services/asg/galleryPathMigration.d.ts +3 -0
- package/build/services/asg/galleryPathMigration.d.ts.map +1 -0
- package/build/services/asg/galleryPathMigration.js +26 -0
- package/build/services/asg/galleryPathMigration.js.map +1 -0
- package/build/services/asg/gallerySettingsService.d.ts +1 -0
- package/build/services/asg/gallerySettingsService.d.ts.map +1 -1
- package/build/services/asg/gallerySettingsService.js +2 -0
- package/build/services/asg/gallerySettingsService.js.map +1 -1
- package/build/services/asg/gallerySyncService.d.ts.map +1 -1
- package/build/services/asg/gallerySyncService.js +162 -56
- package/build/services/asg/gallerySyncService.js.map +1 -1
- package/build/services/asg/galleryTransferLedger.d.ts +58 -0
- package/build/services/asg/galleryTransferLedger.d.ts.map +1 -0
- package/build/services/asg/galleryTransferLedger.js +225 -0
- package/build/services/asg/galleryTransferLedger.js.map +1 -0
- package/build/services/asg/localNetworkTransport.d.ts +15 -0
- package/build/services/asg/localNetworkTransport.d.ts.map +1 -0
- package/build/services/asg/localNetworkTransport.js +144 -0
- package/build/services/asg/localNetworkTransport.js.map +1 -0
- package/build/services/asg/localStorageService.d.ts +18 -0
- package/build/services/asg/localStorageService.d.ts.map +1 -1
- package/build/services/asg/localStorageService.js +168 -19
- package/build/services/asg/localStorageService.js.map +1 -1
- package/build/services/asg/mediaProcessingQueue.d.ts +10 -0
- package/build/services/asg/mediaProcessingQueue.d.ts.map +1 -1
- package/build/services/asg/mediaProcessingQueue.js +193 -62
- package/build/services/asg/mediaProcessingQueue.js.map +1 -1
- package/build/services/audioPlaybackAssets.d.ts +8 -0
- package/build/services/audioPlaybackAssets.d.ts.map +1 -0
- package/build/services/audioPlaybackAssets.js +7 -0
- package/build/services/audioPlaybackAssets.js.map +1 -0
- package/build/services/gallerySyncClock.js +1 -1
- package/build/services/gallerySyncClock.js.map +1 -1
- package/build/services/glassesClockSync.d.ts.map +1 -1
- package/build/services/glassesClockSync.js +6 -2
- package/build/services/glassesClockSync.js.map +1 -1
- package/build/services/manifestActions.d.ts +7 -0
- package/build/services/manifestActions.d.ts.map +1 -0
- package/build/services/manifestActions.js +25 -0
- package/build/services/manifestActions.js.map +1 -0
- package/build/services/otaDisplayState.d.ts +23 -12
- package/build/services/otaDisplayState.d.ts.map +1 -1
- package/build/services/otaDisplayState.js +36 -14
- package/build/services/otaDisplayState.js.map +1 -1
- package/build/services/otaInstallPolicy.d.ts +16 -4
- package/build/services/otaInstallPolicy.d.ts.map +1 -1
- package/build/services/otaInstallPolicy.js +24 -3
- package/build/services/otaInstallPolicy.js.map +1 -1
- package/build/services/otaLegacyMapping.d.ts +1 -1
- package/build/services/otaLegacyMapping.d.ts.map +1 -1
- package/build/services/otaLegacyMapping.js.map +1 -1
- package/build/services/otaManifestPolicy.d.ts +14 -0
- package/build/services/otaManifestPolicy.d.ts.map +1 -0
- package/build/services/otaManifestPolicy.js +19 -0
- package/build/services/otaManifestPolicy.js.map +1 -0
- package/build/services/otaManifestUrl.d.ts +3 -0
- package/build/services/otaManifestUrl.d.ts.map +1 -0
- package/build/services/otaManifestUrl.js +38 -0
- package/build/services/otaManifestUrl.js.map +1 -0
- package/build/services/slimStreamStatus.d.ts +11 -0
- package/build/services/slimStreamStatus.d.ts.map +1 -1
- package/build/services/slimStreamStatus.js +14 -4
- package/build/services/slimStreamStatus.js.map +1 -1
- package/build/stores/apps.d.ts.map +1 -1
- package/build/stores/apps.js +11 -0
- package/build/stores/apps.js.map +1 -1
- package/build/stores/bluetoothSettingKeys.d.ts.map +1 -1
- package/build/stores/bluetoothSettingKeys.js +2 -0
- package/build/stores/bluetoothSettingKeys.js.map +1 -1
- package/build/stores/gallerySync.d.ts +2 -0
- package/build/stores/gallerySync.d.ts.map +1 -1
- package/build/stores/gallerySync.js +2 -0
- package/build/stores/gallerySync.js.map +1 -1
- package/build/stores/glasses.d.ts.map +1 -1
- package/build/stores/glasses.js +3 -0
- package/build/stores/glasses.js.map +1 -1
- package/build/stores/settings.d.ts +17 -0
- package/build/stores/settings.d.ts.map +1 -1
- package/build/stores/settings.js +157 -17
- package/build/stores/settings.js.map +1 -1
- package/build/types/applet.d.ts +2 -0
- package/build/types/applet.d.ts.map +1 -1
- package/build/types/applet.js.map +1 -1
- package/build/types/asg.d.ts +7 -0
- package/build/types/asg.d.ts.map +1 -1
- package/build/types/asg.js.map +1 -1
- package/build/types/enums.d.ts +2 -25
- package/build/types/enums.d.ts.map +1 -1
- package/build/types/enums.js +2 -27
- package/build/types/enums.js.map +1 -1
- package/build/types/hardware.d.ts +4 -13
- package/build/types/hardware.d.ts.map +1 -1
- package/build/types/hardware.js +15 -3
- package/build/types/hardware.js.map +1 -1
- package/build/types/index.d.ts +2 -6
- package/build/types/index.d.ts.map +1 -1
- package/build/types/index.js +2 -6
- package/build/types/index.js.map +1 -1
- package/build/utils/configuredDevHost.d.ts +6 -0
- package/build/utils/configuredDevHost.d.ts.map +1 -0
- package/build/utils/configuredDevHost.js +19 -0
- package/build/utils/configuredDevHost.js.map +1 -0
- package/build/utils/devMiniappLaunch.d.ts +19 -8
- package/build/utils/devMiniappLaunch.d.ts.map +1 -1
- package/build/utils/devMiniappLaunch.js +144 -21
- package/build/utils/devMiniappLaunch.js.map +1 -1
- package/build/utils/diagnosticContext.d.ts.map +1 -1
- package/build/utils/diagnosticContext.js +15 -17
- package/build/utils/diagnosticContext.js.map +1 -1
- package/build/utils/hardware/hardware.d.ts.map +1 -1
- package/build/utils/hardware/hardware.js +7 -3
- package/build/utils/hardware/hardware.js.map +1 -1
- package/build/utils/miniappDiagnostics.d.ts +64 -0
- package/build/utils/miniappDiagnostics.d.ts.map +1 -0
- package/build/utils/miniappDiagnostics.js +173 -0
- package/build/utils/miniappDiagnostics.js.map +1 -0
- package/build/utils/permissions/MediaLibraryPermissions.d.ts +11 -0
- package/build/utils/permissions/MediaLibraryPermissions.d.ts.map +1 -1
- package/build/utils/permissions/MediaLibraryPermissions.js +27 -4
- package/build/utils/permissions/MediaLibraryPermissions.js.map +1 -1
- package/build/utils/timers/timers.d.ts.map +1 -1
- package/build/utils/timers/timers.js +55 -18
- package/build/utils/timers/timers.js.map +1 -1
- package/package.json +59 -31
- package/src/assets/silence.wav +0 -0
- package/src/bluetooth-sdk/debug.ts +1 -0
- package/src/bluetooth-sdk/index.ts +2 -0
- package/src/bluetooth-sdk/photo-receiver.ts +2 -0
- package/src/bluetooth-sdk/react.ts +1 -0
- package/src/bluetooth-sdk/types.ts +1 -0
- package/src/engine.ts +9 -4
- package/src/facades/dev.ts +17 -12
- package/src/facades/display.ts +1 -1
- package/src/facades/gallery.ts +1 -1
- package/src/facades/glasses.ts +20 -7
- package/src/facades/glassesSettings.ts +5 -0
- package/src/facades/miniapps.ts +7 -0
- package/src/facades/ota.ts +23 -8
- package/src/facades/pairing.ts +15 -11
- package/src/facades/permissions.ts +14 -0
- package/src/facades/phoneNotifications.ts +4 -13
- package/src/facades/speech.ts +21 -7
- package/src/generated/releaseMetadata.ts +20 -0
- package/src/index.ts +39 -13
- package/src/react/MentraLiveOtaFlow.tsx +856 -0
- package/src/react/index.ts +7 -0
- package/src/react/useEngineSnapshot.ts +16 -0
- package/src/runtime/bootstrap.ts +33 -1
- package/src/runtime/scanQrSeam.ts +20 -0
- package/src/services/AppRegistry.ts +240 -149
- package/src/services/AudioCloudUplink.ts +23 -1
- package/src/services/AudioPlaybackService.ts +287 -38
- package/src/services/BlobStore.ts +22 -9
- package/src/services/CloudAudioSubscriptionSync.ts +13 -1
- package/src/services/CloudClientService.ts +96 -1
- package/src/services/DeviceEventRouter.ts +5 -5
- package/src/services/ForegroundLocationPermission.ts +24 -0
- package/src/services/GlassesSettingsSync.ts +9 -3
- package/src/services/GlassesStatusProjection.ts +16 -8
- package/src/services/HotspotOtaTransport.ts +184 -0
- package/src/services/HotspotShutdown.ts +41 -0
- package/src/services/LocalDisplayManager.ts +112 -21
- package/src/services/LocalMiniappRuntime.ts +967 -346
- package/src/services/LocalMiniappStorage.ts +82 -0
- package/src/services/LocalMiniappUserIdentity.ts +46 -0
- package/src/services/LocalSttFallbackCoordinator.ts +19 -12
- package/src/services/MicStateCoordinator.ts +169 -4
- package/src/services/MiniappLauncher.ts +30 -3
- package/src/services/MiniappLiveness.ts +24 -0
- package/src/services/NativeHttpResponse.ts +6 -0
- package/src/services/OtaArtifactDownloader.ts +312 -0
- package/src/services/OtaAutoChain.ts +106 -0
- package/src/services/OtaErrorMapping.ts +80 -0
- package/src/services/OtaInstallCoordinator.ts +649 -144
- package/src/services/OtaService.ts +11 -5
- package/src/services/OtaUpdateCheckService.ts +181 -21
- package/src/services/PhoneCalendarHelpers.ts +212 -0
- package/src/services/PhoneCalendarService.ts +65 -0
- package/src/services/PhoneCameraFovCoordinator.ts +210 -0
- package/src/services/PhoneNotificationsSync.ts +12 -10
- package/src/services/PhonePhotoCoordinator.ts +116 -18
- package/src/services/PhoneStreamCoordinator.ts +139 -25
- package/src/services/PhoneVideoCoordinator.ts +11 -0
- package/src/services/SceneRenderer.ts +16 -0
- package/src/services/SentenceTtsPipeline.ts +106 -0
- package/src/services/SupportProfileSync.ts +218 -0
- package/src/services/TranscriptionRouting.ts +37 -0
- package/src/services/TtsTextSanitizer.ts +66 -0
- package/src/services/WifiAdbRequest.ts +53 -0
- package/src/services/asg/asgCameraApi.ts +615 -33
- package/src/services/asg/cameraRollExportCoordinator.ts +627 -0
- package/src/services/asg/cameraRollExportLedger.ts +122 -0
- package/src/services/asg/galleryNotices.ts +1 -0
- package/src/services/asg/galleryPathMigration.ts +22 -0
- package/src/services/asg/gallerySettingsService.ts +3 -0
- package/src/services/asg/gallerySyncService.ts +184 -73
- package/src/services/asg/galleryTransferLedger.ts +312 -0
- package/src/services/asg/localNetworkTransport.ts +169 -0
- package/src/services/asg/localStorageService.ts +179 -24
- package/src/services/asg/mediaProcessingQueue.ts +209 -62
- package/src/services/audioPlaybackAssets.ts +8 -0
- package/src/services/gallerySyncClock.ts +1 -1
- package/src/services/glassesClockSync.ts +6 -2
- package/src/services/manifestActions.ts +30 -0
- package/src/services/otaDisplayState.ts +58 -17
- package/src/services/otaInstallPolicy.ts +30 -4
- package/src/services/otaLegacyMapping.ts +1 -1
- package/src/services/otaManifestPolicy.ts +42 -0
- package/src/services/otaManifestUrl.ts +44 -0
- package/src/services/slimStreamStatus.ts +16 -5
- package/src/stores/apps.ts +12 -0
- package/src/stores/bluetoothSettingKeys.ts +2 -0
- package/src/stores/gallerySync.ts +4 -0
- package/src/stores/glasses.ts +3 -0
- package/src/stores/settings.ts +179 -18
- package/src/types/applet.ts +2 -0
- package/src/types/asg.ts +7 -0
- package/src/types/enums.ts +3 -28
- package/src/types/hardware.ts +20 -13
- package/src/types/index.ts +2 -6
- package/src/utils/configuredDevHost.ts +17 -0
- package/src/utils/devMiniappLaunch.ts +151 -23
- package/src/utils/diagnosticContext.ts +17 -17
- package/src/utils/hardware/hardware.ts +7 -9
- package/src/utils/miniappDiagnostics.ts +226 -0
- package/src/utils/permissions/MediaLibraryPermissions.ts +35 -4
- package/src/utils/timers/timers.ts +57 -21
- package/build/devtools.d.ts +0 -11
- package/build/devtools.d.ts.map +0 -1
- package/build/devtools.js +0 -13
- package/build/devtools.js.map +0 -1
- package/build/internal.d.ts +0 -66
- package/build/internal.d.ts.map +0 -1
- package/build/internal.js +0 -89
- package/build/internal.js.map +0 -1
- package/build/services/asgOtaVersionUrl.d.ts +0 -2
- package/build/services/asgOtaVersionUrl.d.ts.map +0 -1
- package/build/services/asgOtaVersionUrl.js +0 -36
- package/build/services/asgOtaVersionUrl.js.map +0 -1
- package/src/devtools.ts +0 -13
- package/src/internal.ts +0 -158
- package/src/services/asgOtaVersionUrl.ts +0 -41
|
@@ -7,10 +7,13 @@ import { BgTimer } from "../../utils/timers";
|
|
|
7
7
|
import { localStorageService } from "./localStorageService";
|
|
8
8
|
import { validateDownloadedMediaFile } from "./galleryMediaValidation";
|
|
9
9
|
import { reportInvalidGalleryMedia } from "./GalleryMediaIntegrityReportService";
|
|
10
|
+
import { galleryTransferLedger } from "./galleryTransferLedger";
|
|
11
|
+
import { localNetworkTransport } from "./localNetworkTransport";
|
|
10
12
|
export class AsgCameraApiClient {
|
|
11
13
|
baseUrl;
|
|
12
14
|
port;
|
|
13
15
|
lastRequestTime = 0;
|
|
16
|
+
galleryCapabilities;
|
|
14
17
|
constructor(serverUrl, port = 8089) {
|
|
15
18
|
this.port = port;
|
|
16
19
|
this.baseUrl = serverUrl || `http://localhost:${port}`;
|
|
@@ -34,6 +37,7 @@ export class AsgCameraApiClient {
|
|
|
34
37
|
if (this.baseUrl !== newUrl) {
|
|
35
38
|
this.baseUrl = newUrl;
|
|
36
39
|
this.port = newPort;
|
|
40
|
+
this.galleryCapabilities = undefined;
|
|
37
41
|
// console.log(`[ASG Camera API] Server changed from ${oldUrl} to ${this.baseUrl}`)
|
|
38
42
|
}
|
|
39
43
|
else {
|
|
@@ -49,10 +53,9 @@ export class AsgCameraApiClient {
|
|
|
49
53
|
/**
|
|
50
54
|
* Rate limiting helper - ensures minimum delay between requests
|
|
51
55
|
*/
|
|
52
|
-
async rateLimit() {
|
|
56
|
+
async rateLimit(minDelay = 500) {
|
|
53
57
|
const now = Date.now();
|
|
54
58
|
const timeSinceLastRequest = now - this.lastRequestTime;
|
|
55
|
-
const minDelay = 500; // 500ms minimum delay between requests
|
|
56
59
|
if (timeSinceLastRequest < minDelay) {
|
|
57
60
|
const delay = minDelay - timeSinceLastRequest;
|
|
58
61
|
console.log(`[ASG Camera API] Rate limiting: waiting ${delay}ms`);
|
|
@@ -63,7 +66,7 @@ export class AsgCameraApiClient {
|
|
|
63
66
|
/**
|
|
64
67
|
* Make a request to the ASG Camera Server with rate limiting and retry logic
|
|
65
68
|
*/
|
|
66
|
-
async makeRequest(endpoint, options, retries = 5) {
|
|
69
|
+
async makeRequest(endpoint, options, retries = 5, writeIntervalMs = 500) {
|
|
67
70
|
const url = `${this.baseUrl}${endpoint}`;
|
|
68
71
|
const method = options?.method || "GET";
|
|
69
72
|
console.log(`[ASG Camera API] makeRequest called with endpoint: ${endpoint}`);
|
|
@@ -79,8 +82,8 @@ export class AsgCameraApiClient {
|
|
|
79
82
|
const startTime = Date.now();
|
|
80
83
|
try {
|
|
81
84
|
// Apply rate limiting only for non-GET requests
|
|
82
|
-
if (method !== "GET") {
|
|
83
|
-
await this.rateLimit();
|
|
85
|
+
if (method !== "GET" && writeIntervalMs > 0) {
|
|
86
|
+
await this.rateLimit(writeIntervalMs);
|
|
84
87
|
}
|
|
85
88
|
// Prepare headers - don't set Content-Type for GET requests
|
|
86
89
|
const headers = {
|
|
@@ -96,7 +99,7 @@ export class AsgCameraApiClient {
|
|
|
96
99
|
console.log(`[ASG Camera API] Making fetch request to: ${url}`);
|
|
97
100
|
console.log(`[ASG Camera API] Headers being sent:`, headers);
|
|
98
101
|
// N4: Add 30s timeout to all fetch calls in makeRequest
|
|
99
|
-
const response = await fetch(url, {
|
|
102
|
+
const response = await localNetworkTransport.fetch(url, {
|
|
100
103
|
headers,
|
|
101
104
|
...options,
|
|
102
105
|
signal: options?.signal || this.createTimeoutSignal(30000),
|
|
@@ -117,7 +120,7 @@ export class AsgCameraApiClient {
|
|
|
117
120
|
const retryDelay = Math.min(Math.pow(2, 6 - retries) * 1000, 10000);
|
|
118
121
|
console.log(`[ASG Camera API] Rate limited, retrying in ${retryDelay}ms (${retries} retries left)`);
|
|
119
122
|
await new Promise((resolve) => BgTimer.setTimeout(() => resolve(), retryDelay));
|
|
120
|
-
return this.makeRequest(endpoint, options, retries - 1);
|
|
123
|
+
return this.makeRequest(endpoint, options, retries - 1, writeIntervalMs);
|
|
121
124
|
}
|
|
122
125
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
123
126
|
}
|
|
@@ -126,7 +129,12 @@ export class AsgCameraApiClient {
|
|
|
126
129
|
console.log(`[ASG Camera API] Response content-type: ${contentType}`);
|
|
127
130
|
if (contentType?.includes("application/json")) {
|
|
128
131
|
const data = await response.json();
|
|
129
|
-
console.log(`[ASG Camera API] JSON
|
|
132
|
+
console.log(`[ASG Camera API] JSON response received`, {
|
|
133
|
+
endpoint,
|
|
134
|
+
status: data?.status,
|
|
135
|
+
apiVersion: data?.data?.api_version,
|
|
136
|
+
captureCount: data?.data?.captures?.length,
|
|
137
|
+
});
|
|
130
138
|
return data;
|
|
131
139
|
}
|
|
132
140
|
else if (contentType?.includes("image/") || contentType?.includes("application/octet-stream")) {
|
|
@@ -218,7 +226,7 @@ export class AsgCameraApiClient {
|
|
|
218
226
|
// Use browser-like headers since we know the browser works
|
|
219
227
|
try {
|
|
220
228
|
console.log(`[ASG Camera API] Making direct fetch to gallery endpoint`);
|
|
221
|
-
const response = await fetch(galleryUrl, {
|
|
229
|
+
const response = await localNetworkTransport.fetch(galleryUrl, {
|
|
222
230
|
method: "GET",
|
|
223
231
|
headers: {
|
|
224
232
|
"Accept": "application/json",
|
|
@@ -338,7 +346,7 @@ export class AsgCameraApiClient {
|
|
|
338
346
|
for (const endpoint of testEndpoints) {
|
|
339
347
|
try {
|
|
340
348
|
console.log(`[ASG Camera API] Testing endpoint: ${endpoint}`);
|
|
341
|
-
const response = await fetch(`${this.baseUrl}${endpoint}`, {
|
|
349
|
+
const response = await localNetworkTransport.fetch(`${this.baseUrl}${endpoint}`, {
|
|
342
350
|
method: "HEAD",
|
|
343
351
|
headers: {
|
|
344
352
|
"Accept": "*/*",
|
|
@@ -360,7 +368,7 @@ export class AsgCameraApiClient {
|
|
|
360
368
|
if (endpoint === "/api/gallery") {
|
|
361
369
|
try {
|
|
362
370
|
console.log(`[ASG Camera API] Trying GET request for /api/gallery...`);
|
|
363
|
-
const getResponse = await fetch(`${this.baseUrl}${endpoint}`, {
|
|
371
|
+
const getResponse = await localNetworkTransport.fetch(`${this.baseUrl}${endpoint}`, {
|
|
364
372
|
method: "GET",
|
|
365
373
|
headers: {
|
|
366
374
|
"Accept": "application/json",
|
|
@@ -454,7 +462,7 @@ export class AsgCameraApiClient {
|
|
|
454
462
|
// Use a simple HEAD request to check reachability
|
|
455
463
|
const controller = new AbortController();
|
|
456
464
|
const timeoutId = BgTimer.setTimeout(() => controller.abort(), 3000); // 3 second timeout
|
|
457
|
-
const response = await fetch(`${this.baseUrl}/api/health`, {
|
|
465
|
+
const response = await localNetworkTransport.fetch(`${this.baseUrl}/api/health`, {
|
|
458
466
|
method: "HEAD",
|
|
459
467
|
signal: controller.signal,
|
|
460
468
|
});
|
|
@@ -502,6 +510,90 @@ export class AsgCameraApiClient {
|
|
|
502
510
|
});
|
|
503
511
|
return response;
|
|
504
512
|
}
|
|
513
|
+
/** Probe the additive gallery protocol. A 404 cleanly falls back to legacy v2. */
|
|
514
|
+
async getGalleryCapabilities() {
|
|
515
|
+
if (this.galleryCapabilities !== undefined)
|
|
516
|
+
return this.galleryCapabilities;
|
|
517
|
+
try {
|
|
518
|
+
const response = await localNetworkTransport.fetch(`${this.baseUrl}/api/v3/capabilities`, {
|
|
519
|
+
headers: { "Accept": "application/json", "User-Agent": "MentraOS-Mobile/1.0" },
|
|
520
|
+
signal: this.createTimeoutSignal(5000),
|
|
521
|
+
});
|
|
522
|
+
if (response.status === 404) {
|
|
523
|
+
this.galleryCapabilities = null;
|
|
524
|
+
return null;
|
|
525
|
+
}
|
|
526
|
+
if (!response.ok)
|
|
527
|
+
throw new Error(`HTTP ${response.status}`);
|
|
528
|
+
const payload = await response.json();
|
|
529
|
+
const capabilities = (payload.data || payload);
|
|
530
|
+
this.galleryCapabilities = capabilities.api_version >= 3 ? capabilities : null;
|
|
531
|
+
return this.galleryCapabilities;
|
|
532
|
+
}
|
|
533
|
+
catch (error) {
|
|
534
|
+
// A failed probe must not break old firmware. Do not permanently cache transient failures.
|
|
535
|
+
console.warn("[ASG Camera API] v3 capability probe failed; using legacy sync for this attempt", error);
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
/** Fetch all lightweight v3 manifest pages, newest first. */
|
|
540
|
+
async getV3Manifest() {
|
|
541
|
+
const capabilities = await this.getGalleryCapabilities();
|
|
542
|
+
if (!capabilities)
|
|
543
|
+
return null;
|
|
544
|
+
const captures = [];
|
|
545
|
+
let cursor;
|
|
546
|
+
let serverTime = Date.now();
|
|
547
|
+
let totalCount = 0;
|
|
548
|
+
do {
|
|
549
|
+
const params = new URLSearchParams({ limit: String(capabilities.max_manifest_page_size || 100) });
|
|
550
|
+
if (cursor)
|
|
551
|
+
params.set("cursor", cursor);
|
|
552
|
+
const response = await this.makeRequest(`/api/v3/manifest?${params.toString()}`);
|
|
553
|
+
const data = response.data || response;
|
|
554
|
+
captures.push(...(data.captures || []));
|
|
555
|
+
serverTime = data.server_time || serverTime;
|
|
556
|
+
totalCount = data.total_count || captures.length;
|
|
557
|
+
cursor = data.has_more && data.next_cursor ? data.next_cursor : undefined;
|
|
558
|
+
} while (cursor);
|
|
559
|
+
return {
|
|
560
|
+
api_version: 3,
|
|
561
|
+
captures,
|
|
562
|
+
has_more: false,
|
|
563
|
+
next_cursor: null,
|
|
564
|
+
total_count: totalCount,
|
|
565
|
+
server_time: serverTime,
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
async getV3FileHash(fileName) {
|
|
569
|
+
const response = await localNetworkTransport.fetch(`${this.baseUrl}/api/v3/hash?file=${encodeURIComponent(fileName)}`, {
|
|
570
|
+
headers: { "Accept": "application/json", "User-Agent": "MentraOS-Mobile/1.0" },
|
|
571
|
+
signal: this.createTimeoutSignal(10 * 60 * 1000),
|
|
572
|
+
}, 10 * 60 * 1000);
|
|
573
|
+
if (!response.ok)
|
|
574
|
+
throw new Error(`Hash request failed: HTTP ${response.status}`);
|
|
575
|
+
const payload = await response.json();
|
|
576
|
+
return (payload.data || payload);
|
|
577
|
+
}
|
|
578
|
+
async acknowledgeCapture(captureId, ackId) {
|
|
579
|
+
const response = await this.makeRequest("/api/v3/ack", {
|
|
580
|
+
method: "POST",
|
|
581
|
+
body: JSON.stringify({ capture_id: captureId, ack_id: ackId }),
|
|
582
|
+
}, 5, 0);
|
|
583
|
+
const data = response.data || response;
|
|
584
|
+
if (!data.success && !data.already_trashed) {
|
|
585
|
+
throw new Error(data.message || `Glasses did not acknowledge ${captureId}`);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
async restoreCapture(captureId) {
|
|
589
|
+
const response = await this.makeRequest("/api/v3/restore", {
|
|
590
|
+
method: "POST",
|
|
591
|
+
body: JSON.stringify({ capture_id: captureId }),
|
|
592
|
+
}, 5, 0);
|
|
593
|
+
const data = response.data || response;
|
|
594
|
+
if (!data.success)
|
|
595
|
+
throw new Error(data.message || `Glasses did not restore ${captureId}`);
|
|
596
|
+
}
|
|
505
597
|
/**
|
|
506
598
|
* Batch sync files from server with controlled concurrency.
|
|
507
599
|
* Used by the legacy executeDownload path for old asg_client firmware
|
|
@@ -572,11 +664,6 @@ export class AsgCameraApiClient {
|
|
|
572
664
|
results.failed.push(result.error);
|
|
573
665
|
}
|
|
574
666
|
}
|
|
575
|
-
// Small delay between batches to prevent overwhelming the server
|
|
576
|
-
if (i + CONCURRENCY_LIMIT < files.length) {
|
|
577
|
-
console.log(`[ASG Camera API] Waiting 300ms between batches`);
|
|
578
|
-
await new Promise((resolve) => BgTimer.setTimeout(() => resolve(), 300));
|
|
579
|
-
}
|
|
580
667
|
}
|
|
581
668
|
console.log(`[ASG Camera API] Batch sync completed: ${results.downloaded.length} downloaded, ${results.failed.length} failed`);
|
|
582
669
|
return results;
|
|
@@ -585,7 +672,287 @@ export class AsgCameraApiClient {
|
|
|
585
672
|
* Download all files in a capture group sequentially.
|
|
586
673
|
* Reports aggregate byte progress across all files in the group.
|
|
587
674
|
*/
|
|
675
|
+
async downloadResumableCaptureFile(captureId, file, localFilePath, onProgress, abortSignal) {
|
|
676
|
+
const segmentBytes = 16 * 1024 * 1024;
|
|
677
|
+
const segmentCount = Math.max(1, Math.ceil(file.size / segmentBytes));
|
|
678
|
+
const generation = (file.etag || `${file.size}`).replace(/[^A-Za-z0-9_-]/g, "").slice(0, 48) || "legacy";
|
|
679
|
+
const partPath = (index) => `${localFilePath}.partial.${generation}.${index}`;
|
|
680
|
+
const assemblingPath = `${localFilePath}.assembling.${generation}`;
|
|
681
|
+
const transferStartedAt = Date.now();
|
|
682
|
+
let networkDurationMs = 0;
|
|
683
|
+
let networkBytes = 0;
|
|
684
|
+
if (await RNFS.exists(localFilePath)) {
|
|
685
|
+
const existing = await RNFS.stat(localFilePath);
|
|
686
|
+
if (existing.size === file.size) {
|
|
687
|
+
if (file.etag) {
|
|
688
|
+
const remoteHash = file.sha256 ? { sha256: file.sha256, etag: file.etag } : await this.getV3FileHash(file.name);
|
|
689
|
+
if (remoteHash.etag && remoteHash.etag !== file.etag) {
|
|
690
|
+
throw new Error("Source generation changed before existing-file verification");
|
|
691
|
+
}
|
|
692
|
+
const localHash = await RNFS.hash(localFilePath, "sha256");
|
|
693
|
+
if (localHash.toLowerCase() === remoteHash.sha256.toLowerCase()) {
|
|
694
|
+
galleryTransferLedger.markFileHash(captureId, file.name, localHash.toLowerCase());
|
|
695
|
+
onProgress?.(file.size);
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
console.warn(`[ASG Camera API] Existing file failed SHA-256 verification; replacing ${file.name}`);
|
|
699
|
+
}
|
|
700
|
+
else {
|
|
701
|
+
// Legacy servers have no generation/hash endpoint. Preserve old-client
|
|
702
|
+
// compatibility and let the media validator below reject corrupt data.
|
|
703
|
+
onProgress?.(file.size);
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
await RNFS.moveFile(localFilePath, `${localFilePath}.recovery.${Date.now()}`).catch(() => { });
|
|
708
|
+
}
|
|
709
|
+
let completedBytes = 0;
|
|
710
|
+
let resumedSegmentCount = 0;
|
|
711
|
+
for (let index = 0; index < segmentCount; index++) {
|
|
712
|
+
const start = index * segmentBytes;
|
|
713
|
+
const expectedLength = Math.min(segmentBytes, file.size - start);
|
|
714
|
+
if (await RNFS.exists(partPath(index))) {
|
|
715
|
+
const stat = await RNFS.stat(partPath(index));
|
|
716
|
+
if (stat.size === expectedLength) {
|
|
717
|
+
completedBytes += expectedLength;
|
|
718
|
+
resumedSegmentCount++;
|
|
719
|
+
galleryTransferLedger.markSegmentComplete(captureId, file.name, index);
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
722
|
+
await RNFS.unlink(partPath(index)).catch(() => { });
|
|
723
|
+
}
|
|
724
|
+
let completed = false;
|
|
725
|
+
let lastError;
|
|
726
|
+
for (let attempt = 0; attempt < 5 && !completed; attempt++) {
|
|
727
|
+
if (abortSignal?.aborted)
|
|
728
|
+
throw new Error("Sync cancelled");
|
|
729
|
+
await RNFS.unlink(partPath(index)).catch(() => { });
|
|
730
|
+
const end = start + expectedLength - 1;
|
|
731
|
+
let responseHeaders = {};
|
|
732
|
+
const { jobId, promise } = localNetworkTransport.downloadFile({
|
|
733
|
+
fromUrl: `${this.baseUrl}/api/download?file=${encodeURIComponent(file.name)}`,
|
|
734
|
+
toFile: partPath(index),
|
|
735
|
+
headers: {
|
|
736
|
+
"Accept": "*/*",
|
|
737
|
+
"User-Agent": "MentraOS-Mobile/1.0",
|
|
738
|
+
"Range": `bytes=${start}-${end}`,
|
|
739
|
+
...(file.etag ? { "If-Range": file.etag } : {}),
|
|
740
|
+
},
|
|
741
|
+
connectionTimeout: 300000,
|
|
742
|
+
readTimeout: 300000,
|
|
743
|
+
backgroundTimeout: 24 * 60 * 60 * 1000,
|
|
744
|
+
progressDivider: 2,
|
|
745
|
+
progressInterval: 250,
|
|
746
|
+
begin: (response) => {
|
|
747
|
+
responseHeaders = response.headers || {};
|
|
748
|
+
},
|
|
749
|
+
progress: (progress) => onProgress?.(completedBytes + (progress.bytesWritten || 0)),
|
|
750
|
+
});
|
|
751
|
+
let abortPollTimer;
|
|
752
|
+
if (abortSignal) {
|
|
753
|
+
abortPollTimer = BgTimer.setInterval(() => {
|
|
754
|
+
if (abortSignal.aborted)
|
|
755
|
+
localNetworkTransport.stopDownload(jobId);
|
|
756
|
+
}, 500);
|
|
757
|
+
}
|
|
758
|
+
const attemptStartedAt = Date.now();
|
|
759
|
+
try {
|
|
760
|
+
const result = await promise;
|
|
761
|
+
const attemptDurationMs = Math.max(1, Date.now() - attemptStartedAt);
|
|
762
|
+
networkDurationMs += attemptDurationMs;
|
|
763
|
+
networkBytes += result.bytesWritten || 0;
|
|
764
|
+
if (abortSignal?.aborted)
|
|
765
|
+
throw new Error("Sync cancelled");
|
|
766
|
+
const header = (name) => {
|
|
767
|
+
const key = Object.keys(responseHeaders).find((candidate) => candidate.toLowerCase() === name.toLowerCase());
|
|
768
|
+
return key ? responseHeaders[key] : undefined;
|
|
769
|
+
};
|
|
770
|
+
if (result.statusCode === 200 && index === 0 && result.bytesWritten === file.size) {
|
|
771
|
+
// Older servers ignore Range. A full fixed/chunked response is still compatible;
|
|
772
|
+
// commit it through the same staging path and skip the remaining segments.
|
|
773
|
+
await RNFS.unlink(assemblingPath).catch(() => { });
|
|
774
|
+
await RNFS.moveFile(partPath(index), assemblingPath);
|
|
775
|
+
completedBytes = file.size;
|
|
776
|
+
completed = true;
|
|
777
|
+
break;
|
|
778
|
+
}
|
|
779
|
+
if (result.statusCode !== 206)
|
|
780
|
+
throw new Error(`HTTP ${result.statusCode}; expected 206`);
|
|
781
|
+
if (result.bytesWritten !== expectedLength) {
|
|
782
|
+
throw new Error(`Segment ${index} truncated: ${result.bytesWritten}/${expectedLength} bytes`);
|
|
783
|
+
}
|
|
784
|
+
const contentRange = header("content-range");
|
|
785
|
+
if (contentRange !== `bytes ${start}-${end}/${file.size}`) {
|
|
786
|
+
throw new Error(`Invalid Content-Range: ${contentRange || "missing"}`);
|
|
787
|
+
}
|
|
788
|
+
const responseEtag = header("etag");
|
|
789
|
+
if (file.etag && responseEtag && responseEtag !== file.etag) {
|
|
790
|
+
throw new Error("Source generation changed during transfer");
|
|
791
|
+
}
|
|
792
|
+
galleryTransferLedger.markSegmentComplete(captureId, file.name, index);
|
|
793
|
+
completedBytes += expectedLength;
|
|
794
|
+
completed = true;
|
|
795
|
+
console.log("[ASG Camera API] Gallery segment complete", {
|
|
796
|
+
captureId,
|
|
797
|
+
file: file.name,
|
|
798
|
+
segment: index,
|
|
799
|
+
segmentCount,
|
|
800
|
+
attempt: attempt + 1,
|
|
801
|
+
status: result.statusCode,
|
|
802
|
+
bytes: result.bytesWritten,
|
|
803
|
+
durationMs: attemptDurationMs,
|
|
804
|
+
throughputMbps: (((result.bytesWritten || 0) * 8) / attemptDurationMs / 1000).toFixed(2),
|
|
805
|
+
etag: file.etag,
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
catch (error) {
|
|
809
|
+
lastError = error;
|
|
810
|
+
if (error instanceof Error && error.message === "Sync cancelled")
|
|
811
|
+
throw error;
|
|
812
|
+
console.warn("[ASG Camera API] Gallery segment attempt failed", {
|
|
813
|
+
captureId,
|
|
814
|
+
file: file.name,
|
|
815
|
+
segment: index,
|
|
816
|
+
segmentCount,
|
|
817
|
+
attempt: attempt + 1,
|
|
818
|
+
rangeStart: start,
|
|
819
|
+
rangeEnd: end,
|
|
820
|
+
message: error instanceof Error ? error.message : String(error),
|
|
821
|
+
});
|
|
822
|
+
await RNFS.unlink(partPath(index)).catch(() => { });
|
|
823
|
+
if (attempt < 4) {
|
|
824
|
+
const delay = Math.min(1000 * 2 ** attempt, 10000) + Math.floor(Math.random() * 500);
|
|
825
|
+
await new Promise((resolve) => BgTimer.setTimeout(resolve, delay));
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
finally {
|
|
829
|
+
if (abortPollTimer !== undefined)
|
|
830
|
+
BgTimer.clearInterval(abortPollTimer);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
if (!completed)
|
|
834
|
+
throw lastError || new Error(`Segment ${index} failed`);
|
|
835
|
+
if (completedBytes === file.size && (await RNFS.exists(assemblingPath)))
|
|
836
|
+
break;
|
|
837
|
+
}
|
|
838
|
+
if (!(await RNFS.exists(assemblingPath))) {
|
|
839
|
+
await RNFS.unlink(assemblingPath).catch(() => { });
|
|
840
|
+
await RNFS.writeFile(assemblingPath, "", "utf8");
|
|
841
|
+
for (let index = 0; index < segmentCount; index++) {
|
|
842
|
+
const data = await RNFS.readFile(partPath(index), "base64");
|
|
843
|
+
await RNFS.appendFile(assemblingPath, data, "base64");
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
const assembledStat = await RNFS.stat(assemblingPath);
|
|
847
|
+
if (assembledStat.size !== file.size) {
|
|
848
|
+
await RNFS.moveFile(assemblingPath, `${assemblingPath}.invalid.${Date.now()}`).catch(() => { });
|
|
849
|
+
throw new Error(`Assembled file has ${assembledStat.size}/${file.size} bytes`);
|
|
850
|
+
}
|
|
851
|
+
const verificationStartedAt = Date.now();
|
|
852
|
+
const capabilities = await this.getGalleryCapabilities();
|
|
853
|
+
if (capabilities?.sha256) {
|
|
854
|
+
const remoteHash = file.sha256 ? { sha256: file.sha256, etag: file.etag } : await this.getV3FileHash(file.name);
|
|
855
|
+
if (file.etag && remoteHash.etag && file.etag !== remoteHash.etag) {
|
|
856
|
+
throw new Error("Source generation changed before integrity verification");
|
|
857
|
+
}
|
|
858
|
+
const localHash = await RNFS.hash(assemblingPath, "sha256");
|
|
859
|
+
if (localHash.toLowerCase() !== remoteHash.sha256.toLowerCase()) {
|
|
860
|
+
await RNFS.moveFile(assemblingPath, `${assemblingPath}.invalid.${Date.now()}`).catch(() => { });
|
|
861
|
+
throw new Error(`SHA-256 mismatch for ${file.name}`);
|
|
862
|
+
}
|
|
863
|
+
galleryTransferLedger.markFileHash(captureId, file.name, localHash.toLowerCase());
|
|
864
|
+
}
|
|
865
|
+
await RNFS.moveFile(assemblingPath, localFilePath);
|
|
866
|
+
for (let index = 0; index < segmentCount; index++)
|
|
867
|
+
await RNFS.unlink(partPath(index)).catch(() => { });
|
|
868
|
+
console.log("[ASG Camera API] Gallery file committed", {
|
|
869
|
+
captureId,
|
|
870
|
+
file: file.name,
|
|
871
|
+
bytes: file.size,
|
|
872
|
+
segments: segmentCount,
|
|
873
|
+
networkDurationMs,
|
|
874
|
+
networkMbps: networkDurationMs > 0 ? ((networkBytes * 8) / networkDurationMs / 1000).toFixed(2) : "0.00",
|
|
875
|
+
verificationDurationMs: Date.now() - verificationStartedAt,
|
|
876
|
+
totalDurationMs: Date.now() - transferStartedAt,
|
|
877
|
+
resumedSegments: resumedSegmentCount,
|
|
878
|
+
});
|
|
879
|
+
onProgress?.(file.size);
|
|
880
|
+
}
|
|
588
881
|
async downloadCapture(capture, onProgress, abortSignal) {
|
|
882
|
+
if (capture.files.some((file) => !file.size || file.size <= 0)) {
|
|
883
|
+
// Preserve support for malformed manifests from pre-v2 development firmware.
|
|
884
|
+
return this.downloadCaptureLegacyUnsafe(capture, onProgress, abortSignal);
|
|
885
|
+
}
|
|
886
|
+
const captureDir = localStorageService.getPhotoFilePath(capture.capture_id);
|
|
887
|
+
if (!(await RNFS.exists(captureDir)))
|
|
888
|
+
await RNFS.mkdir(captureDir);
|
|
889
|
+
const capabilities = await this.getGalleryCapabilities();
|
|
890
|
+
galleryTransferLedger.ensureCapture(capture, capabilities?.api_version || 2);
|
|
891
|
+
galleryTransferLedger.transition(capture.capture_id, "TRANSFERRING");
|
|
892
|
+
const sortedFiles = [...capture.files].sort((a, b) => {
|
|
893
|
+
const order = { bracket: 0, primary: 1, sidecar: 2 };
|
|
894
|
+
return (order[a.role] ?? 1) - (order[b.role] ?? 1);
|
|
895
|
+
});
|
|
896
|
+
let completedCaptureBytes = 0;
|
|
897
|
+
let primaryPath = "";
|
|
898
|
+
const bracketPaths = [];
|
|
899
|
+
let sidecarPath;
|
|
900
|
+
try {
|
|
901
|
+
for (const file of sortedFiles) {
|
|
902
|
+
const leafName = file.name.includes("/") ? file.name.split("/").pop() : file.name;
|
|
903
|
+
const localFilePath = `${captureDir}/${leafName}`;
|
|
904
|
+
await this.downloadResumableCaptureFile(capture.capture_id, file, localFilePath, (fileBytes) => onProgress?.(completedCaptureBytes + fileBytes, capture.total_size), abortSignal);
|
|
905
|
+
const isVideo = !!file.name.match(/\.(mp4|mov|avi|webm|mkv)$/i);
|
|
906
|
+
const mediaKind = file.role === "sidecar" ? "unknown" : isVideo ? "video" : "photo";
|
|
907
|
+
try {
|
|
908
|
+
await validateDownloadedMediaFile({
|
|
909
|
+
path: localFilePath,
|
|
910
|
+
name: file.name,
|
|
911
|
+
expectedSize: file.size,
|
|
912
|
+
mediaKind,
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
catch (validationError) {
|
|
916
|
+
reportInvalidGalleryMedia({
|
|
917
|
+
name: file.name,
|
|
918
|
+
path: localFilePath,
|
|
919
|
+
mediaKind,
|
|
920
|
+
stage: "download_capture_validation",
|
|
921
|
+
reason: validationError?.message || String(validationError),
|
|
922
|
+
expectedSize: file.size,
|
|
923
|
+
captureId: capture.capture_id,
|
|
924
|
+
duration: capture.duration,
|
|
925
|
+
});
|
|
926
|
+
await RNFS.moveFile(localFilePath, `${localFilePath}.invalid.${Date.now()}`).catch(() => { });
|
|
927
|
+
throw validationError;
|
|
928
|
+
}
|
|
929
|
+
completedCaptureBytes += file.size;
|
|
930
|
+
if (file.role === "primary")
|
|
931
|
+
primaryPath = localFilePath;
|
|
932
|
+
else if (file.role === "bracket")
|
|
933
|
+
bracketPaths.push(localFilePath);
|
|
934
|
+
else if (file.role === "sidecar")
|
|
935
|
+
sidecarPath = localFilePath;
|
|
936
|
+
}
|
|
937
|
+
if (!primaryPath && sortedFiles.length > 0) {
|
|
938
|
+
const leafName = sortedFiles[0].name.includes("/") ? sortedFiles[0].name.split("/").pop() : sortedFiles[0].name;
|
|
939
|
+
primaryPath = `${captureDir}/${leafName}`;
|
|
940
|
+
}
|
|
941
|
+
const primaryFile = sortedFiles.find((file) => file.role === "primary") || sortedFiles[0];
|
|
942
|
+
const thumbnailPath = capture.type === "video" && primaryFile
|
|
943
|
+
? await this.downloadVideoThumbnail(primaryFile.name, `${captureDir}/.thumb.jpg`, abortSignal)
|
|
944
|
+
: undefined;
|
|
945
|
+
galleryTransferLedger.transition(capture.capture_id, "VERIFIED");
|
|
946
|
+
onProgress?.(capture.total_size, capture.total_size);
|
|
947
|
+
return { captureDir, primaryPath, bracketPaths, sidecarPath, thumbnailPath };
|
|
948
|
+
}
|
|
949
|
+
catch (error) {
|
|
950
|
+
galleryTransferLedger.recordFailure(capture.capture_id, error);
|
|
951
|
+
throw error;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
/** Previous direct-to-final implementation kept for binary-source compatibility only. */
|
|
955
|
+
async downloadCaptureLegacyUnsafe(capture, onProgress, abortSignal) {
|
|
589
956
|
const captureDir = localStorageService.getPhotoFilePath(capture.capture_id);
|
|
590
957
|
console.log(`[ASG Camera API] downloadCapture: ${capture.capture_id} (${capture.files.length} files) -> ${captureDir}`);
|
|
591
958
|
// Ensure capture directory exists
|
|
@@ -616,7 +983,7 @@ export class AsgCameraApiClient {
|
|
|
616
983
|
try {
|
|
617
984
|
// Throttle progress: only fire when bytesWritten changes meaningfully
|
|
618
985
|
let lastReportedBytes = -1;
|
|
619
|
-
const { jobId, promise: dlPromise } =
|
|
986
|
+
const { jobId, promise: dlPromise } = localNetworkTransport.downloadFile({
|
|
620
987
|
fromUrl: downloadUrl,
|
|
621
988
|
toFile: localFilePath,
|
|
622
989
|
headers: {
|
|
@@ -640,12 +1007,12 @@ export class AsgCameraApiClient {
|
|
|
640
1007
|
let abortPollTimer;
|
|
641
1008
|
if (abortSignal) {
|
|
642
1009
|
if (abortSignal.aborted) {
|
|
643
|
-
|
|
1010
|
+
localNetworkTransport.stopDownload(jobId);
|
|
644
1011
|
throw new Error("Sync cancelled");
|
|
645
1012
|
}
|
|
646
1013
|
abortPollTimer = BgTimer.setInterval(() => {
|
|
647
1014
|
if (abortSignal.aborted) {
|
|
648
|
-
|
|
1015
|
+
localNetworkTransport.stopDownload(jobId);
|
|
649
1016
|
}
|
|
650
1017
|
}, 500);
|
|
651
1018
|
}
|
|
@@ -720,7 +1087,69 @@ export class AsgCameraApiClient {
|
|
|
720
1087
|
if (onProgress) {
|
|
721
1088
|
onProgress(totalBytes, totalBytes);
|
|
722
1089
|
}
|
|
723
|
-
|
|
1090
|
+
const primaryFile = sortedFiles.find((file) => file.role === "primary") || sortedFiles[0];
|
|
1091
|
+
const thumbnailPath = capture.type === "video" && primaryFile
|
|
1092
|
+
? await this.downloadVideoThumbnail(primaryFile.name, `${captureDir}/.thumb.jpg`, abortSignal)
|
|
1093
|
+
: undefined;
|
|
1094
|
+
return { captureDir, primaryPath, bracketPaths, sidecarPath, thumbnailPath };
|
|
1095
|
+
}
|
|
1096
|
+
/** Download an on-demand video thumbnail while the source capture is still on the glasses. */
|
|
1097
|
+
async downloadVideoThumbnail(sourceFileName, thumbnailPath, abortSignal) {
|
|
1098
|
+
const partialPath = `${thumbnailPath}.partial`;
|
|
1099
|
+
const parentDir = thumbnailPath.substring(0, thumbnailPath.lastIndexOf("/"));
|
|
1100
|
+
try {
|
|
1101
|
+
if (parentDir && !(await RNFS.exists(parentDir))) {
|
|
1102
|
+
await RNFS.mkdir(parentDir);
|
|
1103
|
+
}
|
|
1104
|
+
await RNFS.unlink(partialPath).catch(() => { });
|
|
1105
|
+
const { jobId, promise } = localNetworkTransport.downloadFile({
|
|
1106
|
+
fromUrl: `${this.baseUrl}/api/photo?file=${encodeURIComponent(sourceFileName)}`,
|
|
1107
|
+
toFile: partialPath,
|
|
1108
|
+
headers: {
|
|
1109
|
+
"Accept": "image/jpeg",
|
|
1110
|
+
"User-Agent": "MentraOS-Mobile/1.0",
|
|
1111
|
+
},
|
|
1112
|
+
connectionTimeout: 60000,
|
|
1113
|
+
readTimeout: 60000,
|
|
1114
|
+
progressDivider: 10,
|
|
1115
|
+
});
|
|
1116
|
+
let abortPollTimer;
|
|
1117
|
+
if (abortSignal) {
|
|
1118
|
+
if (abortSignal.aborted) {
|
|
1119
|
+
localNetworkTransport.stopDownload(jobId);
|
|
1120
|
+
throw new Error("Sync cancelled");
|
|
1121
|
+
}
|
|
1122
|
+
abortPollTimer = BgTimer.setInterval(() => {
|
|
1123
|
+
if (abortSignal.aborted)
|
|
1124
|
+
localNetworkTransport.stopDownload(jobId);
|
|
1125
|
+
}, 500);
|
|
1126
|
+
}
|
|
1127
|
+
let result;
|
|
1128
|
+
try {
|
|
1129
|
+
result = await promise;
|
|
1130
|
+
}
|
|
1131
|
+
finally {
|
|
1132
|
+
if (abortPollTimer !== undefined)
|
|
1133
|
+
BgTimer.clearInterval(abortPollTimer);
|
|
1134
|
+
}
|
|
1135
|
+
if (abortSignal?.aborted)
|
|
1136
|
+
throw new Error("Sync cancelled");
|
|
1137
|
+
if (result.statusCode !== 200 || result.bytesWritten <= 0) {
|
|
1138
|
+
throw new Error(`HTTP ${result.statusCode}, ${result.bytesWritten} bytes`);
|
|
1139
|
+
}
|
|
1140
|
+
await RNFS.unlink(thumbnailPath).catch(() => { });
|
|
1141
|
+
await RNFS.moveFile(partialPath, thumbnailPath);
|
|
1142
|
+
console.log(`[ASG Camera API] Downloaded video thumbnail for ${sourceFileName}`);
|
|
1143
|
+
return thumbnailPath;
|
|
1144
|
+
}
|
|
1145
|
+
catch (error) {
|
|
1146
|
+
await RNFS.unlink(partialPath).catch(() => { });
|
|
1147
|
+
if (abortSignal?.aborted || (error instanceof Error && error.message === "Sync cancelled")) {
|
|
1148
|
+
throw new Error("Sync cancelled");
|
|
1149
|
+
}
|
|
1150
|
+
console.warn(`[ASG Camera API] Failed to download video thumbnail for ${sourceFileName}:`, error);
|
|
1151
|
+
return undefined;
|
|
1152
|
+
}
|
|
724
1153
|
}
|
|
725
1154
|
/**
|
|
726
1155
|
* Delete files from server
|
|
@@ -733,7 +1162,7 @@ export class AsgCameraApiClient {
|
|
|
733
1162
|
const response = await this.makeRequest("/api/delete-files", {
|
|
734
1163
|
method: "POST",
|
|
735
1164
|
body: JSON.stringify({ files: fileNames }),
|
|
736
|
-
});
|
|
1165
|
+
}, 5, 0);
|
|
737
1166
|
// Parse the response format from the ASG server
|
|
738
1167
|
const responseData = response;
|
|
739
1168
|
if (responseData.data && responseData.data.results) {
|
|
@@ -770,6 +1199,60 @@ export class AsgCameraApiClient {
|
|
|
770
1199
|
* Download a file from the server and save to filesystem
|
|
771
1200
|
*/
|
|
772
1201
|
async downloadFile(filename, includeThumbnail = false, onProgress, abortSignal, expectedSize) {
|
|
1202
|
+
// The legacy public shape did not require a size. Keep that rare call compatible while all
|
|
1203
|
+
// sync manifests use the verified resumable path below.
|
|
1204
|
+
if (!expectedSize || expectedSize <= 0) {
|
|
1205
|
+
return this.downloadFileLegacyUnsafe(filename, includeThumbnail, onProgress, abortSignal, expectedSize);
|
|
1206
|
+
}
|
|
1207
|
+
const localFilePath = localStorageService.getPhotoFilePath(filename);
|
|
1208
|
+
const parentDir = localFilePath.substring(0, localFilePath.lastIndexOf("/"));
|
|
1209
|
+
if (!(await RNFS.exists(parentDir)))
|
|
1210
|
+
await RNFS.mkdir(parentDir);
|
|
1211
|
+
const lowerName = filename.toLowerCase();
|
|
1212
|
+
const isVideo = /\.(mp4|mov|avi|webm|mkv)$/.test(lowerName);
|
|
1213
|
+
const captureId = filename.includes("/")
|
|
1214
|
+
? filename.substring(0, filename.indexOf("/"))
|
|
1215
|
+
: filename.replace(/\.imu\.json$/i, "").replace(/\.[^.]+$/, "");
|
|
1216
|
+
const capture = {
|
|
1217
|
+
capture_id: captureId,
|
|
1218
|
+
type: isVideo ? "video" : "photo",
|
|
1219
|
+
timestamp: Date.now(),
|
|
1220
|
+
total_size: expectedSize,
|
|
1221
|
+
files: [{ name: filename, size: expectedSize, role: "primary" }],
|
|
1222
|
+
};
|
|
1223
|
+
const capabilities = await this.getGalleryCapabilities();
|
|
1224
|
+
galleryTransferLedger.ensureCapture(capture, capabilities?.api_version || 1);
|
|
1225
|
+
galleryTransferLedger.transition(captureId, "TRANSFERRING");
|
|
1226
|
+
try {
|
|
1227
|
+
await this.downloadResumableCaptureFile(captureId, capture.files[0], localFilePath, (bytes) => onProgress?.(Math.min(100, Math.round((bytes / expectedSize) * 100))), abortSignal);
|
|
1228
|
+
await validateDownloadedMediaFile({
|
|
1229
|
+
path: localFilePath,
|
|
1230
|
+
name: filename,
|
|
1231
|
+
expectedSize,
|
|
1232
|
+
mediaKind: isVideo ? "video" : "photo",
|
|
1233
|
+
});
|
|
1234
|
+
galleryTransferLedger.transition(captureId, "VERIFIED");
|
|
1235
|
+
const thumbnailPath = includeThumbnail && isVideo
|
|
1236
|
+
? await this.downloadVideoThumbnail(filename, localStorageService.getThumbnailFilePath(filename), abortSignal)
|
|
1237
|
+
: undefined;
|
|
1238
|
+
return {
|
|
1239
|
+
filePath: localFilePath,
|
|
1240
|
+
thumbnailPath,
|
|
1241
|
+
mime_type: isVideo
|
|
1242
|
+
? "video/mp4"
|
|
1243
|
+
: lowerName.endsWith(".png")
|
|
1244
|
+
? "image/png"
|
|
1245
|
+
: lowerName.endsWith(".avif")
|
|
1246
|
+
? "image/avif"
|
|
1247
|
+
: "image/jpeg",
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
catch (error) {
|
|
1251
|
+
galleryTransferLedger.recordFailure(captureId, error);
|
|
1252
|
+
throw error;
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
async downloadFileLegacyUnsafe(filename, includeThumbnail = false, onProgress, abortSignal, expectedSize) {
|
|
773
1256
|
console.log(`[ASG Camera API] Downloading file: ${filename}`);
|
|
774
1257
|
try {
|
|
775
1258
|
// Get the local file path where we'll save this
|
|
@@ -795,7 +1278,7 @@ export class AsgCameraApiClient {
|
|
|
795
1278
|
let expectedContentLength = 0;
|
|
796
1279
|
// Throttle progress: only call onProgress when percentage actually changes
|
|
797
1280
|
let lastReportedProgress = -1;
|
|
798
|
-
const { jobId, promise: downloadPromise } =
|
|
1281
|
+
const { jobId, promise: downloadPromise } = localNetworkTransport.downloadFile({
|
|
799
1282
|
fromUrl: downloadUrl,
|
|
800
1283
|
toFile: localFilePath,
|
|
801
1284
|
headers: {
|
|
@@ -836,12 +1319,12 @@ export class AsgCameraApiClient {
|
|
|
836
1319
|
let abortPollTimer;
|
|
837
1320
|
if (abortSignal) {
|
|
838
1321
|
if (abortSignal.aborted) {
|
|
839
|
-
|
|
1322
|
+
localNetworkTransport.stopDownload(jobId);
|
|
840
1323
|
throw new Error("Sync cancelled");
|
|
841
1324
|
}
|
|
842
1325
|
abortPollTimer = BgTimer.setInterval(() => {
|
|
843
1326
|
if (abortSignal.aborted) {
|
|
844
|
-
|
|
1327
|
+
localNetworkTransport.stopDownload(jobId);
|
|
845
1328
|
}
|
|
846
1329
|
}, 500);
|
|
847
1330
|
}
|
|
@@ -938,7 +1421,7 @@ export class AsgCameraApiClient {
|
|
|
938
1421
|
console.log(`[ASG Camera API] Using /api/photo endpoint for video thumbnail`);
|
|
939
1422
|
// The server's /api/photo endpoint serves thumbnails for video files
|
|
940
1423
|
// It detects video files and automatically generates/serves thumbnails instead of the full video
|
|
941
|
-
const thumbResult = await
|
|
1424
|
+
const thumbResult = await localNetworkTransport.downloadFile({
|
|
942
1425
|
fromUrl: `${this.baseUrl}/api/photo?file=${encodeURIComponent(filename)}`,
|
|
943
1426
|
toFile: localThumbnailPath,
|
|
944
1427
|
headers: {
|