@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
|
@@ -0,0 +1,627 @@
|
|
|
1
|
+
import * as RNFS from "@dr.pogodin/react-native-fs"
|
|
2
|
+
|
|
3
|
+
import {MediaLibraryPermissions} from "../../utils/permissions/MediaLibraryPermissions"
|
|
4
|
+
import {BgTimer} from "../../utils/timers"
|
|
5
|
+
import {
|
|
6
|
+
cameraRollEntryId,
|
|
7
|
+
cameraRollExportLedger,
|
|
8
|
+
cameraRollGeneration,
|
|
9
|
+
type CameraRollExportEntry,
|
|
10
|
+
type CameraRollExportPriority,
|
|
11
|
+
} from "./cameraRollExportLedger"
|
|
12
|
+
import {gallerySettingsService} from "./gallerySettingsService"
|
|
13
|
+
import {galleryTransferLedger, type GalleryAssetReceipt} from "./galleryTransferLedger"
|
|
14
|
+
import {localStorageService, type DownloadedFile} from "./localStorageService"
|
|
15
|
+
|
|
16
|
+
const TAG = "[CameraRollExport]"
|
|
17
|
+
const MIN_FREE_SPACE_BYTES = 100 * 1024 * 1024
|
|
18
|
+
const MAX_RETRY_DELAY_MS = 30 * 60 * 1000
|
|
19
|
+
|
|
20
|
+
export interface CameraRollExportSummary {
|
|
21
|
+
enabled: boolean
|
|
22
|
+
total: number
|
|
23
|
+
exported: number
|
|
24
|
+
pending: number
|
|
25
|
+
failed: number
|
|
26
|
+
blockedPermission: number
|
|
27
|
+
fullAccessRequired: number
|
|
28
|
+
missing: number
|
|
29
|
+
bytesPending: number
|
|
30
|
+
isRunning: boolean
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
type SummaryListener = (summary: CameraRollExportSummary) => void
|
|
34
|
+
|
|
35
|
+
class CameraRollExportCoordinator {
|
|
36
|
+
private initialized = false
|
|
37
|
+
private initializing: Promise<void> | null = null
|
|
38
|
+
private lifecycleGeneration = 0
|
|
39
|
+
private enabled = true
|
|
40
|
+
private running = false
|
|
41
|
+
private rerunRequested = false
|
|
42
|
+
private retryTimer: number | null = null
|
|
43
|
+
private activeEntryId: string | null = null
|
|
44
|
+
private clearInProgress = false
|
|
45
|
+
private deletingMediaNames = new Set<string>()
|
|
46
|
+
private listeners = new Set<SummaryListener>()
|
|
47
|
+
private sourceWaiters = new Map<
|
|
48
|
+
string,
|
|
49
|
+
Array<{resolve: (receipt: GalleryAssetReceipt) => void; reject: (error: Error) => void}>
|
|
50
|
+
>()
|
|
51
|
+
|
|
52
|
+
async initialize(): Promise<void> {
|
|
53
|
+
while (!this.initialized) {
|
|
54
|
+
if (!this.initializing) {
|
|
55
|
+
const generation = this.lifecycleGeneration
|
|
56
|
+
this.initializing = this.initializeInternal(generation)
|
|
57
|
+
}
|
|
58
|
+
const initialization = this.initializing
|
|
59
|
+
try {
|
|
60
|
+
await initialization
|
|
61
|
+
} finally {
|
|
62
|
+
if (this.initializing === initialization) this.initializing = null
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
cleanup(): void {
|
|
68
|
+
this.lifecycleGeneration += 1
|
|
69
|
+
if (this.retryTimer != null) BgTimer.clearTimeout(this.retryTimer)
|
|
70
|
+
this.retryTimer = null
|
|
71
|
+
this.initialized = false
|
|
72
|
+
this.rerunRequested = false
|
|
73
|
+
const error = new Error("Camera-roll export coordinator stopped")
|
|
74
|
+
for (const id of [...this.sourceWaiters.keys()]) this.rejectSourceWaiters(id, error)
|
|
75
|
+
if (!this.running) this.activeEntryId = null
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private async initializeInternal(generation: number): Promise<void> {
|
|
79
|
+
cameraRollExportLedger.setDocumentDirectory(RNFS.DocumentDirectoryPath)
|
|
80
|
+
// A native camera-roll insert is atomic and cannot be cancelled. On a quick engine
|
|
81
|
+
// stop/start, let the old drain commit its receipt before the new lifecycle reconciles.
|
|
82
|
+
while (this.running) {
|
|
83
|
+
await new Promise<void>((resolve) => BgTimer.setTimeout(resolve, 25))
|
|
84
|
+
}
|
|
85
|
+
if (generation !== this.lifecycleGeneration) return
|
|
86
|
+
const enabled = await gallerySettingsService.getAutoSaveToCameraRoll()
|
|
87
|
+
if (generation !== this.lifecycleGeneration) return
|
|
88
|
+
this.enabled = enabled
|
|
89
|
+
await this.reconcileLocalIndex()
|
|
90
|
+
if (generation !== this.lifecycleGeneration) return
|
|
91
|
+
this.initialized = true
|
|
92
|
+
this.emitSummary()
|
|
93
|
+
if (this.enabled) void this.resume("startup")
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Rebuild missing per-generation records after upgrading from legacy clients. */
|
|
97
|
+
private async reconcileLocalIndex(): Promise<void> {
|
|
98
|
+
const files = await localStorageService.getDownloadedFiles()
|
|
99
|
+
const liveIds = new Set<string>()
|
|
100
|
+
const liveGenerationByMediaName = new Map<string, string>()
|
|
101
|
+
for (const file of Object.values(files)) {
|
|
102
|
+
const id = cameraRollEntryId(file.name, file.size, file.modified)
|
|
103
|
+
liveIds.add(id)
|
|
104
|
+
liveGenerationByMediaName.set(file.name, id)
|
|
105
|
+
const current = cameraRollExportLedger.get(id)
|
|
106
|
+
const transfer = file.capture_id
|
|
107
|
+
? galleryTransferLedger.get(file.capture_id)
|
|
108
|
+
: galleryTransferLedger.get(file.name)
|
|
109
|
+
const receipt = file.assetReceipt || transfer?.assetReceipt || current?.receipt
|
|
110
|
+
const now = Date.now()
|
|
111
|
+
cameraRollExportLedger.put({
|
|
112
|
+
...(current || {
|
|
113
|
+
id,
|
|
114
|
+
mediaName: file.name,
|
|
115
|
+
generation: cameraRollGeneration(file.name, file.size, file.modified),
|
|
116
|
+
filePath: file.filePath,
|
|
117
|
+
size: file.size,
|
|
118
|
+
modified: file.modified,
|
|
119
|
+
isVideo: file.is_video,
|
|
120
|
+
captureId: file.capture_id || transfer?.captureId,
|
|
121
|
+
priority: "HISTORICAL_BACKFILL" as const,
|
|
122
|
+
attempts: 0,
|
|
123
|
+
createdAt: now,
|
|
124
|
+
updatedAt: now,
|
|
125
|
+
state: "LEGACY_UNKNOWN" as const,
|
|
126
|
+
}),
|
|
127
|
+
filePath: file.filePath,
|
|
128
|
+
localPresent: true,
|
|
129
|
+
receipt,
|
|
130
|
+
requiresFullLibraryReconciliation:
|
|
131
|
+
current?.requiresFullLibraryReconciliation ?? (!current && !receipt && !transfer),
|
|
132
|
+
state: receipt ? "EXPORTED" : this.nextEligibleState(current?.state),
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Explicit app deletion removes receipts through deleteLocalMedia/clearLocalMedia. An index
|
|
137
|
+
// miss here is not proof of deletion: recovery may not have rebuilt the row yet, and a load
|
|
138
|
+
// failure is represented as an empty index. Preserve exported receipts as hidden tombstones;
|
|
139
|
+
// they are the authoritative proof that prevents a duplicate native-library insert.
|
|
140
|
+
for (const entry of cameraRollExportLedger.list()) {
|
|
141
|
+
if (liveIds.has(entry.id)) continue
|
|
142
|
+
if (entry.receipt) {
|
|
143
|
+
cameraRollExportLedger.update(entry.id, {state: "EXPORTED", localPresent: false})
|
|
144
|
+
continue
|
|
145
|
+
}
|
|
146
|
+
// A different live generation with the same display name supersedes an unexported stale
|
|
147
|
+
// row. Never let its old path export the replacement's bytes under the wrong fingerprint.
|
|
148
|
+
if (liveGenerationByMediaName.has(entry.mediaName)) {
|
|
149
|
+
cameraRollExportLedger.remove(entry.id)
|
|
150
|
+
continue
|
|
151
|
+
}
|
|
152
|
+
cameraRollExportLedger.update(entry.id, {
|
|
153
|
+
state: "MISSING_LOCAL",
|
|
154
|
+
localPresent: false,
|
|
155
|
+
lastError: "Local media is missing",
|
|
156
|
+
nextRetryAt: undefined,
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private nextEligibleState(current?: CameraRollExportEntry["state"]): CameraRollExportEntry["state"] {
|
|
162
|
+
if (!this.enabled) return "NOT_REQUESTED"
|
|
163
|
+
// Reconciliation only calls this for files that are present in the durable local index.
|
|
164
|
+
// A previous filesystem miss was transient if that same generation is visible again.
|
|
165
|
+
if (current === "FAILED_PERMANENT") return current
|
|
166
|
+
return "QUEUED"
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async recordIndexed(
|
|
170
|
+
file: DownloadedFile,
|
|
171
|
+
shouldAutoSave: boolean,
|
|
172
|
+
priority: CameraRollExportPriority = "HISTORICAL_BACKFILL",
|
|
173
|
+
captureId?: string,
|
|
174
|
+
): Promise<CameraRollExportEntry> {
|
|
175
|
+
await this.initialize()
|
|
176
|
+
const id = cameraRollEntryId(file.name, file.size, file.modified)
|
|
177
|
+
const current = cameraRollExportLedger.get(id)
|
|
178
|
+
const now = Date.now()
|
|
179
|
+
const entry = cameraRollExportLedger.put({
|
|
180
|
+
...(current || {
|
|
181
|
+
id,
|
|
182
|
+
mediaName: file.name,
|
|
183
|
+
generation: cameraRollGeneration(file.name, file.size, file.modified),
|
|
184
|
+
attempts: 0,
|
|
185
|
+
createdAt: now,
|
|
186
|
+
updatedAt: now,
|
|
187
|
+
}),
|
|
188
|
+
filePath: file.filePath,
|
|
189
|
+
size: file.size,
|
|
190
|
+
modified: file.modified,
|
|
191
|
+
isVideo: file.is_video,
|
|
192
|
+
captureId: captureId || file.capture_id || current?.captureId,
|
|
193
|
+
localPresent: true,
|
|
194
|
+
requiresFullLibraryReconciliation: current?.requiresFullLibraryReconciliation ?? false,
|
|
195
|
+
priority,
|
|
196
|
+
receipt: file.assetReceipt || current?.receipt,
|
|
197
|
+
state: file.assetReceipt || current?.receipt ? "EXPORTED" : shouldAutoSave ? "QUEUED" : "NOT_REQUESTED",
|
|
198
|
+
lastError: undefined,
|
|
199
|
+
nextRetryAt: undefined,
|
|
200
|
+
})
|
|
201
|
+
this.emitSummary()
|
|
202
|
+
return entry
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async isAutoSaveEnabled(): Promise<boolean> {
|
|
206
|
+
await this.initialize()
|
|
207
|
+
return this.enabled
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Source deletion waits on this barrier while enabled; disabled exports remain safely app-local. */
|
|
211
|
+
async exportForSource(file: DownloadedFile, captureId?: string): Promise<GalleryAssetReceipt | undefined> {
|
|
212
|
+
await this.initialize()
|
|
213
|
+
// A durable receipt proves this generation is already safe regardless of the current
|
|
214
|
+
// preference. Return it before enforcing disabled state so recovery can still ack the
|
|
215
|
+
// glasses after a previously-started export committed.
|
|
216
|
+
const entry = await this.recordIndexed(file, this.enabled, "SOURCE_BARRIER", captureId)
|
|
217
|
+
if (entry.receipt) return entry.receipt
|
|
218
|
+
if (!this.enabled) return undefined
|
|
219
|
+
try {
|
|
220
|
+
return await new Promise<GalleryAssetReceipt>((resolve, reject) => {
|
|
221
|
+
const current = cameraRollExportLedger.get(entry.id)
|
|
222
|
+
if (current?.receipt) {
|
|
223
|
+
resolve(current.receipt)
|
|
224
|
+
return
|
|
225
|
+
}
|
|
226
|
+
if (!current) {
|
|
227
|
+
reject(new Error("Camera-roll export was removed before its source barrier registered"))
|
|
228
|
+
return
|
|
229
|
+
}
|
|
230
|
+
const waiters = this.sourceWaiters.get(entry.id) || []
|
|
231
|
+
waiters.push({resolve, reject})
|
|
232
|
+
this.sourceWaiters.set(entry.id, waiters)
|
|
233
|
+
// Re-read after registration so any future refactor that yields while installing a
|
|
234
|
+
// waiter still cannot miss a receipt committed by the active runner.
|
|
235
|
+
const registered = cameraRollExportLedger.get(entry.id)
|
|
236
|
+
if (registered?.receipt) {
|
|
237
|
+
this.resolveSourceWaiters(entry.id, registered.receipt)
|
|
238
|
+
return
|
|
239
|
+
}
|
|
240
|
+
if (!registered) {
|
|
241
|
+
this.rejectSourceWaiters(
|
|
242
|
+
entry.id,
|
|
243
|
+
new Error("Camera-roll export was removed before its source barrier registered"),
|
|
244
|
+
)
|
|
245
|
+
return
|
|
246
|
+
}
|
|
247
|
+
void this.resume("source barrier")
|
|
248
|
+
})
|
|
249
|
+
} catch (error) {
|
|
250
|
+
// Turning auto-save off makes the durable app-local commit the source-of-truth. A native
|
|
251
|
+
// insert already in progress still finishes and returns its receipt; queued work may stop.
|
|
252
|
+
if (!this.enabled) return undefined
|
|
253
|
+
throw error
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async setEnabled(enabled: boolean): Promise<void> {
|
|
258
|
+
await this.initialize()
|
|
259
|
+
const previous = this.enabled
|
|
260
|
+
await gallerySettingsService.setAutoSaveToCameraRoll(enabled)
|
|
261
|
+
try {
|
|
262
|
+
this.enabled = enabled
|
|
263
|
+
if (!enabled && this.retryTimer != null) {
|
|
264
|
+
BgTimer.clearTimeout(this.retryTimer)
|
|
265
|
+
this.retryTimer = null
|
|
266
|
+
}
|
|
267
|
+
for (const entry of cameraRollExportLedger.list()) {
|
|
268
|
+
if (entry.receipt || entry.state === "EXPORTED" || entry.state === "MISSING_LOCAL") continue
|
|
269
|
+
if (!enabled && entry.id !== this.activeEntryId) {
|
|
270
|
+
cameraRollExportLedger.update(entry.id, {state: "NOT_REQUESTED", nextRetryAt: undefined})
|
|
271
|
+
this.rejectSourceWaiters(entry.id, new Error("Automatic camera-roll saving was disabled"))
|
|
272
|
+
} else if (enabled) {
|
|
273
|
+
cameraRollExportLedger.update(entry.id, {state: "QUEUED", nextRetryAt: undefined, lastError: undefined})
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
} catch (error) {
|
|
277
|
+
this.enabled = previous
|
|
278
|
+
await gallerySettingsService.setAutoSaveToCameraRoll(previous)
|
|
279
|
+
throw error
|
|
280
|
+
}
|
|
281
|
+
this.emitSummary()
|
|
282
|
+
if (enabled) void this.resume("setting enabled")
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async resume(reason: string): Promise<void> {
|
|
286
|
+
const generation = this.lifecycleGeneration
|
|
287
|
+
await this.initialize()
|
|
288
|
+
if (generation !== this.lifecycleGeneration) return
|
|
289
|
+
console.log(`${TAG} Resume requested: ${reason}`)
|
|
290
|
+
if (cameraRollExportLedger.list().some((entry) => entry.state === "BLOCKED_PERMISSION")) {
|
|
291
|
+
const hasPermission = await MediaLibraryPermissions.checkPermission()
|
|
292
|
+
const hasLimitedAccess = hasPermission && (await MediaLibraryPermissions.hasLimitedAccess())
|
|
293
|
+
if (generation !== this.lifecycleGeneration) return
|
|
294
|
+
if (hasPermission) {
|
|
295
|
+
for (const entry of cameraRollExportLedger.list()) {
|
|
296
|
+
if (entry.state === "BLOCKED_PERMISSION" && !(entry.requiresFullLibraryReconciliation && hasLimitedAccess)) {
|
|
297
|
+
cameraRollExportLedger.update(entry.id, {state: "QUEUED", lastError: undefined})
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (generation !== this.lifecycleGeneration) return
|
|
303
|
+
if (this.retryTimer != null) {
|
|
304
|
+
BgTimer.clearTimeout(this.retryTimer)
|
|
305
|
+
this.retryTimer = null
|
|
306
|
+
}
|
|
307
|
+
if (this.running) {
|
|
308
|
+
this.rerunRequested = true
|
|
309
|
+
while (this.running) {
|
|
310
|
+
await new Promise<void>((resolve) => BgTimer.setTimeout(resolve, 25))
|
|
311
|
+
}
|
|
312
|
+
if (generation !== this.lifecycleGeneration) return
|
|
313
|
+
// The active drain normally consumes rerunRequested before releasing running. Keep
|
|
314
|
+
// this final durable-work check so a late handoff can never strand an eligible entry.
|
|
315
|
+
if (this.nextEntry()) await this.resume("work remained after concurrent resume")
|
|
316
|
+
return
|
|
317
|
+
}
|
|
318
|
+
this.running = true
|
|
319
|
+
this.emitSummary()
|
|
320
|
+
try {
|
|
321
|
+
do {
|
|
322
|
+
this.rerunRequested = false
|
|
323
|
+
let entry = this.nextEntry()
|
|
324
|
+
while (entry && generation === this.lifecycleGeneration) {
|
|
325
|
+
await this.processEntry(entry, generation)
|
|
326
|
+
if (generation !== this.lifecycleGeneration) break
|
|
327
|
+
entry = this.nextEntry()
|
|
328
|
+
}
|
|
329
|
+
} while (this.rerunRequested && generation === this.lifecycleGeneration)
|
|
330
|
+
} finally {
|
|
331
|
+
this.rerunRequested = false
|
|
332
|
+
this.running = false
|
|
333
|
+
if (generation === this.lifecycleGeneration) {
|
|
334
|
+
this.emitSummary()
|
|
335
|
+
this.scheduleRetry()
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async retryNow(): Promise<void> {
|
|
341
|
+
const generation = this.lifecycleGeneration
|
|
342
|
+
await this.initialize()
|
|
343
|
+
// A recovered transfer may have rebuilt a missing local-index row since the last lifecycle
|
|
344
|
+
// reconciliation. Refresh before retrying so the same camera-roll generation becomes
|
|
345
|
+
// eligible again; never rewrite entry state underneath an active native insert.
|
|
346
|
+
while (this.running) {
|
|
347
|
+
await new Promise<void>((resolve) => BgTimer.setTimeout(resolve, 25))
|
|
348
|
+
}
|
|
349
|
+
if (generation !== this.lifecycleGeneration) return
|
|
350
|
+
await this.reconcileLocalIndex()
|
|
351
|
+
if (generation !== this.lifecycleGeneration) return
|
|
352
|
+
for (const entry of cameraRollExportLedger.list()) {
|
|
353
|
+
if (
|
|
354
|
+
entry.state === "FAILED_RETRYABLE" ||
|
|
355
|
+
entry.state === "BLOCKED_PERMISSION" ||
|
|
356
|
+
(entry.state === "MISSING_LOCAL" && entry.localPresent !== false)
|
|
357
|
+
) {
|
|
358
|
+
cameraRollExportLedger.update(entry.id, {state: "QUEUED", nextRetryAt: undefined, lastError: undefined})
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
await this.resume("manual retry")
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
subscribe(listener: SummaryListener): () => void {
|
|
365
|
+
this.listeners.add(listener)
|
|
366
|
+
// `enabled` is loaded from durable settings during initialize(). Do not emit the
|
|
367
|
+
// default before that read completes or disabled users briefly see the toggle as on.
|
|
368
|
+
if (this.initialized) listener(this.getSummary())
|
|
369
|
+
return () => this.listeners.delete(listener)
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
getSummary(): CameraRollExportSummary {
|
|
373
|
+
const entries = cameraRollExportLedger
|
|
374
|
+
.list()
|
|
375
|
+
// Exported receipts with no local row are hidden deduplication tombstones. Unexported
|
|
376
|
+
// missing generations remain user-visible so data that can no longer be backfilled is
|
|
377
|
+
// never reported as successfully saved.
|
|
378
|
+
.filter((entry) => entry.localPresent !== false || entry.state === "MISSING_LOCAL")
|
|
379
|
+
const pendingStates = new Set([
|
|
380
|
+
"LEGACY_UNKNOWN",
|
|
381
|
+
"QUEUED",
|
|
382
|
+
"RECONCILING",
|
|
383
|
+
"EXPORTING",
|
|
384
|
+
"FAILED_RETRYABLE",
|
|
385
|
+
"BLOCKED_PERMISSION",
|
|
386
|
+
])
|
|
387
|
+
return {
|
|
388
|
+
enabled: this.enabled,
|
|
389
|
+
total: entries.length,
|
|
390
|
+
exported: entries.filter((entry) => entry.state === "EXPORTED").length,
|
|
391
|
+
pending: entries.filter((entry) => pendingStates.has(entry.state)).length,
|
|
392
|
+
failed: entries.filter((entry) => entry.state === "FAILED_RETRYABLE" || entry.state === "FAILED_PERMANENT")
|
|
393
|
+
.length,
|
|
394
|
+
blockedPermission: entries.filter((entry) => entry.state === "BLOCKED_PERMISSION").length,
|
|
395
|
+
fullAccessRequired: entries.filter(
|
|
396
|
+
(entry) => entry.state === "BLOCKED_PERMISSION" && entry.requiresFullLibraryReconciliation,
|
|
397
|
+
).length,
|
|
398
|
+
missing: entries.filter((entry) => entry.state === "MISSING_LOCAL").length,
|
|
399
|
+
bytesPending: entries
|
|
400
|
+
.filter((entry) => pendingStates.has(entry.state))
|
|
401
|
+
.reduce((sum, entry) => sum + entry.size, 0),
|
|
402
|
+
isRunning: this.running,
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
async countNotExported(mediaNames: string[]): Promise<number> {
|
|
407
|
+
await this.initialize()
|
|
408
|
+
const uniqueNames = [...new Set(mediaNames)]
|
|
409
|
+
if (uniqueNames.length === 0) return 0
|
|
410
|
+
const files = await localStorageService.getDownloadedFiles()
|
|
411
|
+
return uniqueNames.filter((name) => {
|
|
412
|
+
const file = files[name]
|
|
413
|
+
if (!file) return true
|
|
414
|
+
const entry = cameraRollExportLedger.get(cameraRollEntryId(file.name, file.size, file.modified))
|
|
415
|
+
return entry?.state !== "EXPORTED"
|
|
416
|
+
}).length
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/** Serialize local deletion against an in-flight native copy of the same item. */
|
|
420
|
+
async deleteLocalMedia(mediaName: string): Promise<boolean> {
|
|
421
|
+
this.deletingMediaNames.add(mediaName)
|
|
422
|
+
try {
|
|
423
|
+
while (this.activeEntryId && cameraRollExportLedger.get(this.activeEntryId)?.mediaName === mediaName) {
|
|
424
|
+
await new Promise<void>((resolve) => BgTimer.setTimeout(resolve, 25))
|
|
425
|
+
}
|
|
426
|
+
const deleted = await localStorageService.deleteDownloadedFile(mediaName)
|
|
427
|
+
if (deleted) {
|
|
428
|
+
// An already-running export gets to finish and resolve its source barrier. Queued work
|
|
429
|
+
// cannot start while the name is in deletingMediaNames, so reject only after deletion
|
|
430
|
+
// is confirmed. A failed delete leaves the barrier intact and eligible for retry.
|
|
431
|
+
for (const entry of cameraRollExportLedger.list().filter((candidate) => candidate.mediaName === mediaName)) {
|
|
432
|
+
this.rejectSourceWaiters(entry.id, new Error("Local media was deleted before camera-roll export"))
|
|
433
|
+
}
|
|
434
|
+
cameraRollExportLedger.removeForMedia(mediaName)
|
|
435
|
+
}
|
|
436
|
+
return deleted
|
|
437
|
+
} finally {
|
|
438
|
+
this.deletingMediaNames.delete(mediaName)
|
|
439
|
+
this.emitSummary()
|
|
440
|
+
if (this.enabled) void this.resume("local deletion finished")
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/** Stop scheduling exports, finish the current native copy, then atomically clear app media + receipts. */
|
|
445
|
+
async clearLocalMedia(): Promise<void> {
|
|
446
|
+
this.clearInProgress = true
|
|
447
|
+
try {
|
|
448
|
+
while (this.activeEntryId) {
|
|
449
|
+
await new Promise<void>((resolve) => BgTimer.setTimeout(resolve, 25))
|
|
450
|
+
}
|
|
451
|
+
await localStorageService.clearAllFiles()
|
|
452
|
+
// Let the active native copy deliver its receipt before rejecting queued barriers, and
|
|
453
|
+
// reject them only after the app media clear is confirmed. If clear fails, the durable
|
|
454
|
+
// work and its waiters remain eligible for retry.
|
|
455
|
+
for (const entry of cameraRollExportLedger.list()) {
|
|
456
|
+
this.rejectSourceWaiters(entry.id, new Error("Local media was deleted before camera-roll export"))
|
|
457
|
+
}
|
|
458
|
+
cameraRollExportLedger.clear()
|
|
459
|
+
} finally {
|
|
460
|
+
this.clearInProgress = false
|
|
461
|
+
this.emitSummary()
|
|
462
|
+
if (this.enabled) void this.resume("local clear finished")
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
private nextEntry(): CameraRollExportEntry | undefined {
|
|
467
|
+
const now = Date.now()
|
|
468
|
+
return cameraRollExportLedger
|
|
469
|
+
.list()
|
|
470
|
+
.filter((entry) => {
|
|
471
|
+
if (entry.localPresent === false) return false
|
|
472
|
+
if (this.clearInProgress || this.deletingMediaNames.has(entry.mediaName)) return false
|
|
473
|
+
if (entry.receipt || entry.state === "EXPORTED" || entry.state === "MISSING_LOCAL") return false
|
|
474
|
+
if (!this.enabled) return false
|
|
475
|
+
if (entry.state === "BLOCKED_PERMISSION" || entry.state === "FAILED_PERMANENT") return false
|
|
476
|
+
if (entry.state === "FAILED_RETRYABLE" && (entry.nextRetryAt || 0) > now) return false
|
|
477
|
+
return entry.state !== "NOT_REQUESTED"
|
|
478
|
+
})
|
|
479
|
+
.sort((a, b) => {
|
|
480
|
+
if (a.priority !== b.priority) return a.priority === "SOURCE_BARRIER" ? -1 : 1
|
|
481
|
+
return a.modified - b.modified
|
|
482
|
+
})[0]
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
private async processEntry(original: CameraRollExportEntry, generation: number): Promise<void> {
|
|
486
|
+
let entry = original
|
|
487
|
+
this.activeEntryId = entry.id
|
|
488
|
+
try {
|
|
489
|
+
if (!this.enabled) {
|
|
490
|
+
cameraRollExportLedger.update(entry.id, {state: "NOT_REQUESTED", nextRetryAt: undefined})
|
|
491
|
+
this.rejectSourceWaiters(entry.id, new Error("Automatic camera-roll saving was disabled"))
|
|
492
|
+
return
|
|
493
|
+
}
|
|
494
|
+
const exists = await RNFS.exists(entry.filePath)
|
|
495
|
+
if (generation !== this.lifecycleGeneration) return
|
|
496
|
+
if (!exists) {
|
|
497
|
+
cameraRollExportLedger.update(entry.id, {state: "MISSING_LOCAL", lastError: "Local media is missing"})
|
|
498
|
+
throw new Error("Local media is missing")
|
|
499
|
+
}
|
|
500
|
+
const permission = await MediaLibraryPermissions.checkPermission()
|
|
501
|
+
if (generation !== this.lifecycleGeneration) return
|
|
502
|
+
if (!permission) {
|
|
503
|
+
cameraRollExportLedger.update(entry.id, {
|
|
504
|
+
state: "BLOCKED_PERMISSION",
|
|
505
|
+
lastError: "Camera-roll permission is required",
|
|
506
|
+
})
|
|
507
|
+
throw new Error("Camera-roll permission is required")
|
|
508
|
+
}
|
|
509
|
+
const hasLimitedAccess =
|
|
510
|
+
entry.requiresFullLibraryReconciliation && (await MediaLibraryPermissions.hasLimitedAccess())
|
|
511
|
+
if (generation !== this.lifecycleGeneration) return
|
|
512
|
+
if (hasLimitedAccess) {
|
|
513
|
+
cameraRollExportLedger.update(entry.id, {
|
|
514
|
+
state: "BLOCKED_PERMISSION",
|
|
515
|
+
lastError: "Full photo-library access is required to reconcile this legacy export safely",
|
|
516
|
+
})
|
|
517
|
+
throw new Error("Full photo-library access is required to reconcile this legacy export safely")
|
|
518
|
+
}
|
|
519
|
+
const fsInfo = await RNFS.getFSInfo()
|
|
520
|
+
if (generation !== this.lifecycleGeneration) return
|
|
521
|
+
if (fsInfo.freeSpace < entry.size + MIN_FREE_SPACE_BYTES) {
|
|
522
|
+
throw new Error("Insufficient free space for camera-roll export")
|
|
523
|
+
}
|
|
524
|
+
// A setting change can land while the filesystem and permission checks are in flight.
|
|
525
|
+
// Once the native save starts it must finish atomically, but do not begin a new save
|
|
526
|
+
// after the user has disabled automatic camera-roll exports.
|
|
527
|
+
if (!this.enabled) {
|
|
528
|
+
cameraRollExportLedger.update(entry.id, {state: "NOT_REQUESTED", nextRetryAt: undefined})
|
|
529
|
+
this.rejectSourceWaiters(entry.id, new Error("Automatic camera-roll saving was disabled"))
|
|
530
|
+
return
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
entry = cameraRollExportLedger.update(entry.id, {state: "RECONCILING", lastError: undefined})
|
|
534
|
+
entry = cameraRollExportLedger.update(entry.id, {state: "EXPORTING"})
|
|
535
|
+
const result = await MediaLibraryPermissions.saveToLibraryWithReceipt(entry.filePath, entry.modified)
|
|
536
|
+
if (!result.success) throw new Error(result.error || "Camera-roll export failed")
|
|
537
|
+
const receipt: GalleryAssetReceipt = {
|
|
538
|
+
platform: result.platform,
|
|
539
|
+
uri: result.uri,
|
|
540
|
+
identifier: result.identifier,
|
|
541
|
+
exportedAt: Date.now(),
|
|
542
|
+
}
|
|
543
|
+
entry = cameraRollExportLedger.update(entry.id, {
|
|
544
|
+
state: "EXPORTED",
|
|
545
|
+
receipt,
|
|
546
|
+
requiresFullLibraryReconciliation: false,
|
|
547
|
+
attempts: entry.attempts + 1,
|
|
548
|
+
nextRetryAt: undefined,
|
|
549
|
+
lastError: undefined,
|
|
550
|
+
})
|
|
551
|
+
try {
|
|
552
|
+
await this.mirrorReceipt(entry, receipt)
|
|
553
|
+
} catch (mirrorError) {
|
|
554
|
+
// The per-generation ledger is the authoritative durable receipt. A secondary
|
|
555
|
+
// metadata mirror failure must not cause a duplicate insert or block source ack.
|
|
556
|
+
console.warn(`${TAG} Export receipt mirror failed for ${entry.mediaName}:`, mirrorError)
|
|
557
|
+
}
|
|
558
|
+
this.resolveSourceWaiters(entry.id, receipt)
|
|
559
|
+
} catch (error) {
|
|
560
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
561
|
+
const current = cameraRollExportLedger.get(entry.id)
|
|
562
|
+
if (current && current.state !== "BLOCKED_PERMISSION" && current.state !== "MISSING_LOCAL") {
|
|
563
|
+
const attempts = current.attempts + 1
|
|
564
|
+
cameraRollExportLedger.update(entry.id, {
|
|
565
|
+
state: "FAILED_RETRYABLE",
|
|
566
|
+
attempts,
|
|
567
|
+
lastError: message,
|
|
568
|
+
nextRetryAt: Date.now() + Math.min(2 ** Math.min(attempts, 10) * 1000, MAX_RETRY_DELAY_MS),
|
|
569
|
+
})
|
|
570
|
+
}
|
|
571
|
+
this.rejectSourceWaiters(entry.id, error instanceof Error ? error : new Error(message))
|
|
572
|
+
console.warn(`${TAG} Export remains pending for ${entry.mediaName}: ${message}`)
|
|
573
|
+
} finally {
|
|
574
|
+
if (this.activeEntryId === entry.id) this.activeEntryId = null
|
|
575
|
+
if (generation === this.lifecycleGeneration) this.emitSummary()
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
private async mirrorReceipt(entry: CameraRollExportEntry, receipt: GalleryAssetReceipt): Promise<void> {
|
|
580
|
+
await localStorageService.updateDownloadedFileAssetReceipt(entry.mediaName, receipt)
|
|
581
|
+
const transfer = entry.captureId
|
|
582
|
+
? galleryTransferLedger.get(entry.captureId)
|
|
583
|
+
: galleryTransferLedger.get(entry.mediaName)
|
|
584
|
+
if (transfer) galleryTransferLedger.update(transfer.captureId, {assetReceipt: receipt})
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
private scheduleRetry(): void {
|
|
588
|
+
if (!this.enabled || this.retryTimer != null) return
|
|
589
|
+
const next = cameraRollExportLedger
|
|
590
|
+
.list()
|
|
591
|
+
.filter((entry) => entry.state === "FAILED_RETRYABLE" && entry.nextRetryAt)
|
|
592
|
+
.sort((a, b) => (a.nextRetryAt || 0) - (b.nextRetryAt || 0))[0]
|
|
593
|
+
if (!next?.nextRetryAt) return
|
|
594
|
+
const generation = this.lifecycleGeneration
|
|
595
|
+
this.retryTimer = BgTimer.setTimeout(
|
|
596
|
+
() => {
|
|
597
|
+
this.retryTimer = null
|
|
598
|
+
if (generation !== this.lifecycleGeneration) return
|
|
599
|
+
void this.resume("retry timer")
|
|
600
|
+
},
|
|
601
|
+
Math.max(0, next.nextRetryAt - Date.now()),
|
|
602
|
+
)
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
private resolveSourceWaiters(id: string, receipt: GalleryAssetReceipt): void {
|
|
606
|
+
for (const waiter of this.sourceWaiters.get(id) || []) waiter.resolve(receipt)
|
|
607
|
+
this.sourceWaiters.delete(id)
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
private rejectSourceWaiters(id: string, error: Error): void {
|
|
611
|
+
for (const waiter of this.sourceWaiters.get(id) || []) waiter.reject(error)
|
|
612
|
+
this.sourceWaiters.delete(id)
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
private emitSummary(): void {
|
|
616
|
+
const summary = this.getSummary()
|
|
617
|
+
for (const listener of this.listeners) {
|
|
618
|
+
try {
|
|
619
|
+
listener(summary)
|
|
620
|
+
} catch (error) {
|
|
621
|
+
console.warn(`${TAG} Summary listener failed`, error)
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
export const cameraRollExportCoordinator = new CameraRollExportCoordinator()
|