@mentra/engine 0.1.0-dev.0 → 3.1.0-beta.100
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 +73 -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/ota-transport.d.ts +2 -0
- package/build/bluetooth-sdk/ota-transport.d.ts.map +1 -0
- package/build/bluetooth-sdk/ota-transport.js +2 -0
- package/build/bluetooth-sdk/ota-transport.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 +29 -18
- 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 +24 -21
- package/build/facades/ota.d.ts.map +1 -1
- package/build/facades/ota.js +31 -12
- package/build/facades/ota.js.map +1 -1
- package/build/facades/pairing.d.ts +12 -5
- package/build/facades/pairing.d.ts.map +1 -1
- package/build/facades/pairing.js +93 -17
- 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 +12 -10
- package/build/index.d.ts.map +1 -1
- package/build/index.js +7 -6
- package/build/index.js.map +1 -1
- package/build/react/MentraLiveOtaFlow.d.ts +44 -0
- package/build/react/MentraLiveOtaFlow.d.ts.map +1 -0
- package/build/react/MentraLiveOtaFlow.js +430 -0
- package/build/react/MentraLiveOtaFlow.js.map +1 -0
- package/build/react/index.d.ts +3 -0
- package/build/react/index.d.ts.map +1 -0
- package/build/react/index.js +3 -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/react/useMentraLiveOta.d.ts +84 -0
- package/build/react/useMentraLiveOta.d.ts.map +1 -0
- package/build/react/useMentraLiveOta.js +627 -0
- package/build/react/useMentraLiveOta.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 +4 -1
- package/build/services/BlobStore.d.ts.map +1 -1
- package/build/services/BlobStore.js +80 -19
- 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/DisplayProcessor.d.ts +3 -3
- package/build/services/DisplayProcessor.js +3 -3
- package/build/services/DisplayProcessor.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 +15 -1
- package/build/services/GlassesStatusProjection.d.ts.map +1 -1
- package/build/services/GlassesStatusProjection.js +18 -11
- 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 +147 -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 +17 -3
- package/build/services/LocalDisplayManager.d.ts.map +1 -1
- package/build/services/LocalDisplayManager.js +108 -23
- 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 +41 -0
- package/build/services/OtaAutoChain.d.ts.map +1 -0
- package/build/services/OtaAutoChain.js +91 -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 +120 -17
- package/build/services/OtaInstallCoordinator.d.ts.map +1 -1
- package/build/services/OtaInstallCoordinator.js +605 -136
- package/build/services/OtaInstallCoordinator.js.map +1 -1
- package/build/services/OtaService.d.ts.map +1 -1
- package/build/services/OtaService.js +9 -2
- package/build/services/OtaService.js.map +1 -1
- package/build/services/OtaUpdateCheckService.d.ts +42 -4
- package/build/services/OtaUpdateCheckService.d.ts.map +1 -1
- package/build/services/OtaUpdateCheckService.js +158 -26
- 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 +80 -10
- 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/StreamLifecycleController.d.ts +3 -3
- package/build/services/StreamLifecycleController.js +3 -3
- package/build/services/StreamLifecycleController.js.map +1 -1
- 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 +145 -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 +7 -3
- 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 +20 -4
- package/build/services/otaInstallPolicy.d.ts.map +1 -1
- package/build/services/otaInstallPolicy.js +28 -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/otaReleaseVersion.d.ts +10 -0
- package/build/services/otaReleaseVersion.d.ts.map +1 -0
- package/build/services/otaReleaseVersion.js +14 -0
- package/build/services/otaReleaseVersion.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/appVisibility.d.ts +3 -0
- package/build/stores/appVisibility.d.ts.map +1 -0
- package/build/stores/appVisibility.js +3 -0
- package/build/stores/appVisibility.js.map +1 -0
- package/build/stores/apps.d.ts.map +1 -1
- package/build/stores/apps.js +18 -1
- 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/core.d.ts +3 -3
- package/build/stores/core.d.ts.map +1 -1
- package/build/stores/core.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 +10 -4
- 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 +3 -1
- package/build/types/applet.d.ts.map +1 -1
- package/build/types/applet.js +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/capabilities/even-realities-g1.d.ts.map +1 -1
- package/build/types/capabilities/even-realities-g1.js +2 -0
- package/build/types/capabilities/even-realities-g1.js.map +1 -1
- package/build/types/capabilities/even-realities-g2.d.ts.map +1 -1
- package/build/types/capabilities/even-realities-g2.js +2 -0
- package/build/types/capabilities/even-realities-g2.js.map +1 -1
- package/build/types/capabilities/mentra-display.d.ts.map +1 -1
- package/build/types/capabilities/mentra-display.js +2 -0
- package/build/types/capabilities/mentra-display.js.map +1 -1
- package/build/types/capabilities/mentra-live.d.ts.map +1 -1
- package/build/types/capabilities/mentra-live.js +2 -0
- package/build/types/capabilities/mentra-live.js.map +1 -1
- package/build/types/capabilities/nimo.d.ts +9 -0
- package/build/types/capabilities/nimo.d.ts.map +1 -0
- package/build/types/capabilities/nimo.js +57 -0
- package/build/types/capabilities/nimo.js.map +1 -0
- package/build/types/capabilities/none.d.ts.map +1 -1
- package/build/types/capabilities/none.js +2 -0
- package/build/types/capabilities/none.js.map +1 -1
- package/build/types/capabilities/simulated-glasses.d.ts.map +1 -1
- package/build/types/capabilities/simulated-glasses.js +2 -0
- package/build/types/capabilities/simulated-glasses.js.map +1 -1
- package/build/types/capabilities/vuzix-z100.d.ts.map +1 -1
- package/build/types/capabilities/vuzix-z100.js +2 -0
- package/build/types/capabilities/vuzix-z100.js.map +1 -1
- package/build/types/enums.d.ts +4 -26
- package/build/types/enums.d.ts.map +1 -1
- package/build/types/enums.js +3 -27
- package/build/types/enums.js.map +1 -1
- package/build/types/hardware.d.ts +6 -14
- package/build/types/hardware.d.ts.map +1 -1
- package/build/types/hardware.js +18 -4
- package/build/types/hardware.js.map +1 -1
- package/build/types/index.d.ts +3 -7
- package/build/types/index.d.ts.map +1 -1
- package/build/types/index.js +3 -7
- 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/display/composer/ColumnComposer.d.ts +1 -1
- package/build/utils/display/composer/ColumnComposer.js +1 -1
- package/build/utils/display/composer/ColumnComposer.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 +66 -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/ota-transport.ts +1 -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 +41 -13
- package/src/facades/pairing.ts +103 -23
- 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 +42 -13
- package/src/react/MentraLiveOtaFlow.tsx +754 -0
- package/src/react/index.ts +21 -0
- package/src/react/useEngineSnapshot.ts +16 -0
- package/src/react/useMentraLiveOta.ts +793 -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 +79 -19
- package/src/services/CloudAudioSubscriptionSync.ts +13 -1
- package/src/services/CloudClientService.ts +96 -1
- package/src/services/DeviceEventRouter.ts +5 -5
- package/src/services/DisplayProcessor.ts +3 -3
- package/src/services/ForegroundLocationPermission.ts +24 -0
- package/src/services/GlassesSettingsSync.ts +9 -3
- package/src/services/GlassesStatusProjection.ts +20 -11
- package/src/services/HotspotOtaTransport.ts +185 -0
- package/src/services/HotspotShutdown.ts +41 -0
- package/src/services/LocalDisplayManager.ts +114 -24
- 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 +130 -0
- package/src/services/OtaErrorMapping.ts +80 -0
- package/src/services/OtaInstallCoordinator.ts +680 -151
- package/src/services/OtaService.ts +12 -7
- package/src/services/OtaUpdateCheckService.ts +205 -27
- 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 +117 -19
- 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/StreamLifecycleController.ts +3 -3
- 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 +170 -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 +7 -3
- package/src/services/manifestActions.ts +30 -0
- package/src/services/otaDisplayState.ts +58 -17
- package/src/services/otaInstallPolicy.ts +34 -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/otaReleaseVersion.ts +22 -0
- package/src/services/slimStreamStatus.ts +16 -5
- package/src/stores/appVisibility.ts +3 -0
- package/src/stores/apps.ts +19 -1
- package/src/stores/bluetoothSettingKeys.ts +2 -0
- package/src/stores/core.ts +4 -4
- package/src/stores/gallerySync.ts +4 -0
- package/src/stores/glasses.ts +16 -6
- package/src/stores/settings.ts +179 -18
- package/src/types/applet.ts +3 -1
- package/src/types/asg.ts +7 -0
- package/src/types/capabilities/even-realities-g1.ts +3 -0
- package/src/types/capabilities/even-realities-g2.ts +3 -0
- package/src/types/capabilities/mentra-display.ts +3 -0
- package/src/types/capabilities/mentra-live.ts +3 -0
- package/src/types/capabilities/nimo.ts +59 -0
- package/src/types/capabilities/none.ts +3 -0
- package/src/types/capabilities/simulated-glasses.ts +3 -0
- package/src/types/capabilities/vuzix-z100.ts +3 -0
- package/src/types/enums.ts +4 -28
- package/src/types/hardware.ts +23 -14
- package/src/types/index.ts +3 -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/display/composer/ColumnComposer.ts +1 -1
- 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
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* OtaInstallCoordinator — engine-owned OTA install state machine (WP 8B).
|
|
3
3
|
*
|
|
4
4
|
* Owns every timer/retry/sequencing rule that used to live in the host screen
|
|
5
|
-
* `mobile/src/app/ota/progress.tsx`: the global session timeout,
|
|
6
|
-
*
|
|
5
|
+
* `mobile/src/app/ota/progress.tsx`: the global session timeout, serialized
|
|
6
|
+
* ota_start retries, the stuck-at-zero watchdog, the progress-stall watchdog keyed
|
|
7
7
|
* on a staleness signature, connect-edge arbitration (initial mount vs
|
|
8
8
|
* reconnect vs post-APK reboot), the ota_query_status reply fallback, the ping
|
|
9
9
|
* keepalive, the ota_start_ack / mtk_update_complete listeners, and terminal
|
|
@@ -11,24 +11,42 @@
|
|
|
11
11
|
* `snapshot()`/`onSnapshot()` plus the `attach()/detach()/retry()/finish()`
|
|
12
12
|
* commands (exposed as `engine.ota.installSession`).
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
* duration (see ./otaInstallPolicy), arbitration rule, and "[OTA_PROGRESS]"
|
|
16
|
-
* log line is a field-debugging contract and stays byte-identical to the old
|
|
17
|
-
* screen. The reaction pass mirrors the old component's effect ordering: a
|
|
14
|
+
* The reaction pass mirrors the old component's effect ordering: a
|
|
18
15
|
* pass diffs the store-derived inputs (connected / otaStatus / otaProgress /
|
|
19
16
|
* displayState / stall signature) exactly like the old effect dep arrays, and
|
|
20
17
|
* state written mid-pass queues a follow-up pass — the same way a setState
|
|
21
18
|
* during React effects scheduled a re-render after the current effect batch.
|
|
22
19
|
*/
|
|
23
|
-
import BluetoothSdk from "@mentra/bluetooth-sdk
|
|
20
|
+
import BluetoothSdk from "@mentra/bluetooth-sdk";
|
|
24
21
|
import GlobalEventEmitter from "../utils/GlobalEventEmitter";
|
|
25
22
|
import { isGlassesConnected, useGlassesStore } from "../stores/glasses";
|
|
26
|
-
import {
|
|
23
|
+
import { resolveOtaManifestUrl } from "./otaManifestUrl";
|
|
24
|
+
import { hotspotOtaTransport } from "./HotspotOtaTransport";
|
|
27
25
|
import { deriveDisplayState } from "./otaDisplayState";
|
|
28
|
-
import { BES_CONTINUE_LOCKOUT_MS, DOWNLOAD_STUCK_TIMEOUT_MS, GLOBAL_OTA_TIMEOUT_MS, LEGACY_APK_COMPLETION_SETTLE_MS, LEGACY_BES_CONTINUE_LOCKOUT_MS, LEGACY_DOWNLOAD_STUCK_TIMEOUT_MS, LEGACY_GLOBAL_OTA_TIMEOUT_MS, LEGACY_MTK_INSTALL_TIMEOUT_MS, LEGACY_MTK_SIM_CAP_PERCENT, LEGACY_MTK_SIM_FLOOR_PERCENT, LEGACY_MTK_SIM_TICK_MS, LEGACY_MTK_STALL_DETECT_MS, LEGACY_MTK_STALL_ZONE_MAX_PERCENT, LEGACY_MTK_STALL_ZONE_MIN_PERCENT, LEGACY_PING_INTERVAL_MS, LEGACY_PROGRESS_TIMEOUT_MS, LEGACY_RETRY_INTERVAL_MS, MAX_RETRIES, MTK_INSTALL_TIMEOUT_MS, OtaProgressMessages, PING_INTERVAL_MS,
|
|
26
|
+
import { BES_CONTINUE_LOCKOUT_MS, BES_RESTART_TIMEOUT_MS, DOWNLOAD_STUCK_TIMEOUT_MS, GLOBAL_OTA_TIMEOUT_MS, LEGACY_APK_COMPLETION_SETTLE_MS, LEGACY_BES_CONTINUE_LOCKOUT_MS, LEGACY_BES_RESTART_TIMEOUT_MS, LEGACY_DOWNLOAD_STUCK_TIMEOUT_MS, LEGACY_GLOBAL_OTA_TIMEOUT_MS, LEGACY_MTK_INSTALL_TIMEOUT_MS, LEGACY_MTK_SIM_CAP_PERCENT, LEGACY_MTK_SIM_FLOOR_PERCENT, LEGACY_MTK_SIM_TICK_MS, LEGACY_MTK_STALL_DETECT_MS, LEGACY_MTK_STALL_ZONE_MAX_PERCENT, LEGACY_MTK_STALL_ZONE_MIN_PERCENT, LEGACY_PING_INTERVAL_MS, LEGACY_PROGRESS_TIMEOUT_MS, LEGACY_RETRY_INTERVAL_MS, MAX_RETRIES, MTK_INSTALL_TIMEOUT_MS, OtaProgressMessages, PING_INTERVAL_MS, PROGRESS_TIMEOUT_MS, QUERY_REPLY_TIMEOUT_MS, RETRY_INTERVAL_MS, isLegacyShapedOtaSession, isLegacyShapedOtaStatus, selectOtaProtocolProfile, } from "./otaInstallPolicy";
|
|
29
27
|
function isTerminalForWatchdog(d) {
|
|
30
28
|
return d === "complete" || d === "failed" || d === "restarting";
|
|
31
29
|
}
|
|
30
|
+
function hotspotPreflightErrorMessage(error) {
|
|
31
|
+
const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
|
|
32
|
+
switch (code) {
|
|
33
|
+
case "artifact_download_failed":
|
|
34
|
+
return OtaProgressMessages.hotspotArtifactDownloadFailed;
|
|
35
|
+
case "artifact_verify_failed":
|
|
36
|
+
case "manifest_invalid":
|
|
37
|
+
return OtaProgressMessages.hotspotArtifactVerifyFailed;
|
|
38
|
+
case "hotspot_wifi_permission_denied":
|
|
39
|
+
return OtaProgressMessages.hotspotWifiPermissionDenied;
|
|
40
|
+
case "hotspot_start_failed":
|
|
41
|
+
return OtaProgressMessages.hotspotStartFailed;
|
|
42
|
+
case "hotspot_join_failed":
|
|
43
|
+
return OtaProgressMessages.hotspotJoinFailed;
|
|
44
|
+
case "hotspot_server_failed":
|
|
45
|
+
return OtaProgressMessages.hotspotServerFailed;
|
|
46
|
+
default:
|
|
47
|
+
return OtaProgressMessages.hotspotServerFailed;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
32
50
|
/** Non-empty when glasses are actively reporting work — drives stall timeout reset. */
|
|
33
51
|
function buildProgressStalenessSignature(otaStatus, otaProgress, displayState) {
|
|
34
52
|
if (displayState !== "starting" && displayState !== "updating")
|
|
@@ -80,6 +98,11 @@ function hasRecoveringOtaReply(otaStatus, otaProgress) {
|
|
|
80
98
|
}
|
|
81
99
|
class OtaInstallCoordinator {
|
|
82
100
|
attached = false;
|
|
101
|
+
preparedCheckResult = null;
|
|
102
|
+
selectedTransport = "wifi";
|
|
103
|
+
hotspotManifestUrl = null;
|
|
104
|
+
hotspotPhase = "idle";
|
|
105
|
+
hotspotArtifactPercent = null;
|
|
83
106
|
// Genuinely session-local state (was component state/refs).
|
|
84
107
|
errorMsg = "";
|
|
85
108
|
sawReconnectEdge = false;
|
|
@@ -97,6 +120,28 @@ class OtaInstallCoordinator {
|
|
|
97
120
|
apkExpectedInSession = false;
|
|
98
121
|
// Latched when the legacy build-number completion fires; projected as "complete".
|
|
99
122
|
apkCompletedViaBuildIncrease = false;
|
|
123
|
+
// The split routes selected legacy/unified behavior before navigation. Keep
|
|
124
|
+
// that choice sticky while an APK step upgrades ASG mid-flow.
|
|
125
|
+
protocolProfile = null;
|
|
126
|
+
// BES reboot recovery. Native bridges normalize explicit BES success to a
|
|
127
|
+
// terminal status; pre-37 flows additionally retain the legacy screen's
|
|
128
|
+
// lost-FINISHED fallback. Completion still requires a BES-specific observed
|
|
129
|
+
// disconnect -> reconnect lifecycle, never the generic session reconnect flag.
|
|
130
|
+
besRestartExpected = false;
|
|
131
|
+
besRestartDisconnectObserved = false;
|
|
132
|
+
besRestartRecovery = null;
|
|
133
|
+
// Downgrade detour (version-change mode). Captured at attach from the selected
|
|
134
|
+
// update: the pinned target is a LOWER build, executed by the recovery worker via
|
|
135
|
+
// uninstall->reinstall, so the phone must NOT drive ota_start on the intermediate
|
|
136
|
+
// reconnect (the passive factory build) and completion is exact-version
|
|
137
|
+
// convergence, not a build-number increase.
|
|
138
|
+
versionChangeSession = false;
|
|
139
|
+
versionChangeTarget = null;
|
|
140
|
+
// Set once ASG reports the apk install has started (the handoff point after which
|
|
141
|
+
// ASG dies and the recovery worker owns the transaction).
|
|
142
|
+
versionChangeInstallStarted = false;
|
|
143
|
+
// Latched when a reconnect reports buildNumber === versionChangeTarget.
|
|
144
|
+
versionChangeConverged = false;
|
|
100
145
|
// Holds a legacy apk install FINISHED out of "complete" for the settle window.
|
|
101
146
|
legacyApkSettleHold = false;
|
|
102
147
|
// Display-only legacy MTK install stall simulation.
|
|
@@ -108,15 +153,21 @@ class OtaInstallCoordinator {
|
|
|
108
153
|
retryTimeout = null;
|
|
109
154
|
stuckTimeout = null;
|
|
110
155
|
progressTimeout = null;
|
|
111
|
-
postApkDelay = null;
|
|
112
156
|
pingInterval = null;
|
|
113
157
|
queryReplyTimeout = null;
|
|
114
158
|
continueLockoutTimer = null;
|
|
159
|
+
restartRecoveryTimeout = null;
|
|
115
160
|
legacyApkSettleTimer = null;
|
|
116
161
|
mtkStallDetectTimer = null;
|
|
117
162
|
mtkSimTickTimer = null;
|
|
118
|
-
//
|
|
119
|
-
|
|
163
|
+
// Request / retry bookkeeping (no glasses source)
|
|
164
|
+
// At most one apk-install status poll in flight (native rejects concurrent queries).
|
|
165
|
+
apkInstallPollInFlight = false;
|
|
166
|
+
// The native SDK owns one ota_start request until its application-level ack or
|
|
167
|
+
// timeout. Keep pending ownership across attach/detach, and retain the settled
|
|
168
|
+
// outcome while attached so Retry/query fallback cannot reinterpret an accepted
|
|
169
|
+
// command as permission to start a second OTA transaction.
|
|
170
|
+
otaStartOwnership = null;
|
|
120
171
|
hasFirstActivity = false;
|
|
121
172
|
// Stuck-at-zero watchdog clears only on first NON-ZERO progress; "first activity"
|
|
122
173
|
// alone (e.g. ota_status with stepPercent=0) used to clear it too eagerly.
|
|
@@ -138,6 +189,24 @@ class OtaInstallCoordinator {
|
|
|
138
189
|
storeUnsubscribe = null;
|
|
139
190
|
snapshotCheckers = new Set();
|
|
140
191
|
// --- public surface (engine.ota.installSession) ---
|
|
192
|
+
/** Select the transport at the existing install entry point before the progress route attaches. */
|
|
193
|
+
prepare(checkResult) {
|
|
194
|
+
const state = useGlassesStore.getState();
|
|
195
|
+
if (!state.wifiStatusKnown) {
|
|
196
|
+
throw new Error("Glasses Wi-Fi status is not available");
|
|
197
|
+
}
|
|
198
|
+
const hotspotSupported = state.hotspotOtaVersion === 1;
|
|
199
|
+
const wifiConnected = state.wifi.state === "connected";
|
|
200
|
+
if (!wifiConnected && !hotspotSupported) {
|
|
201
|
+
throw new Error("Connected glasses require Wi-Fi for OTA");
|
|
202
|
+
}
|
|
203
|
+
this.preparedCheckResult = checkResult;
|
|
204
|
+
this.selectedTransport = wifiConnected ? "wifi" : "hotspot";
|
|
205
|
+
this.hotspotManifestUrl = null;
|
|
206
|
+
this.hotspotPhase = "idle";
|
|
207
|
+
this.hotspotArtifactPercent = null;
|
|
208
|
+
return this.selectedTransport;
|
|
209
|
+
}
|
|
141
210
|
/**
|
|
142
211
|
* Bind the state machine to a mounted progress screen. Idempotent per
|
|
143
212
|
* attach/detach cycle. Runs the initial-mount arbitration (send ota_start
|
|
@@ -149,6 +218,14 @@ class OtaInstallCoordinator {
|
|
|
149
218
|
return;
|
|
150
219
|
this.attached = true;
|
|
151
220
|
this.resetSessionState();
|
|
221
|
+
const initialState = useGlassesStore.getState();
|
|
222
|
+
this.protocolProfile = selectOtaProtocolProfile(initialState.otaStatus, initialState.otaProgress, initialState.buildNumber);
|
|
223
|
+
const selected = initialState.otaUpdateAvailable;
|
|
224
|
+
if (selected?.isDowngrade && typeof selected.versionCode === "number" && selected.versionCode > 0) {
|
|
225
|
+
this.versionChangeSession = true;
|
|
226
|
+
this.versionChangeTarget = selected.versionCode;
|
|
227
|
+
console.log(`[OTA_PROGRESS] version-change (downgrade) session, target=${selected.versionCode}`);
|
|
228
|
+
}
|
|
152
229
|
this.prevConnected = isGlassesConnected(useGlassesStore.getState().connection);
|
|
153
230
|
this.storeUnsubscribe = useGlassesStore.subscribe(() => this.react());
|
|
154
231
|
// Mount pass: every reaction block runs once (like all effects on mount),
|
|
@@ -180,13 +257,63 @@ class OtaInstallCoordinator {
|
|
|
180
257
|
}
|
|
181
258
|
GlobalEventEmitter.off("ota_start_ack", this.handleAck);
|
|
182
259
|
GlobalEventEmitter.off("mtk_update_complete", this.handleMtkComplete);
|
|
260
|
+
GlobalEventEmitter.off("glasses_session_changed", this.handleGlassesSessionChanged);
|
|
183
261
|
this.clearAllOtaTimers();
|
|
184
262
|
this.clearContinueLockoutTimer();
|
|
263
|
+
// Only native command ownership survives a remount. Once the command has
|
|
264
|
+
// settled, the glasses store/durable ota_status is the session authority.
|
|
265
|
+
if (this.otaStartOwnership?.outcome !== "pending") {
|
|
266
|
+
this.otaStartOwnership = null;
|
|
267
|
+
}
|
|
185
268
|
this.resetSessionState();
|
|
186
269
|
}
|
|
187
270
|
/** Retry after a failure: clear state and re-send ota_start (if connected). */
|
|
188
271
|
retry() {
|
|
189
|
-
|
|
272
|
+
// While the recovery worker owns the detour (apk install latched, not yet
|
|
273
|
+
// converged), the phone must not drive: an ota_start now would push the
|
|
274
|
+
// factory/surviving build into a parallel download-install, and a second
|
|
275
|
+
// handoff would begin()/REPLACE the live transaction under the running
|
|
276
|
+
// worker. Retry just re-enters the reconcile wait — convergence or the
|
|
277
|
+
// session timeout decides the real outcome.
|
|
278
|
+
if (this.isInVersionChangeDetour()) {
|
|
279
|
+
console.log("[OTA_PROGRESS] retry during version-change detour — re-entering wait (no ota_start)");
|
|
280
|
+
this.setErrorMsg("");
|
|
281
|
+
this.emitInternalChange();
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const store = useGlassesStore.getState();
|
|
285
|
+
const otaStatus = store.otaStatus;
|
|
286
|
+
const otaProgress = store.otaProgress;
|
|
287
|
+
const authoritativeFailure = otaStatus?.status === "failed" || otaProgress?.status === "FAILED";
|
|
288
|
+
const hasUnfinishedSession = (!!otaStatus &&
|
|
289
|
+
otaStatus.status !== "idle" &&
|
|
290
|
+
otaStatus.status !== "failed" &&
|
|
291
|
+
otaStatus.status !== "complete") ||
|
|
292
|
+
(!!otaProgress && otaProgress.status !== "FAILED" && otaProgress.status !== "FINISHED");
|
|
293
|
+
const shouldReconcile = this.otaStartOwnership?.outcome === "pending" ||
|
|
294
|
+
(!authoritativeFailure &&
|
|
295
|
+
(this.otaStartOwnership?.outcome === "acknowledged" || this.hasFirstActivity || hasUnfinishedSession));
|
|
296
|
+
if (shouldReconcile) {
|
|
297
|
+
console.log("[OTA_PROGRESS] retry pressed for an existing OTA session — reconciling without ota_start");
|
|
298
|
+
this.clearPerStepTimers();
|
|
299
|
+
this.clearLegacyApkSettleTimer();
|
|
300
|
+
this.clearMtkSimulationTimers();
|
|
301
|
+
this.clearContinueLockoutTimer();
|
|
302
|
+
this.setErrorMsg("");
|
|
303
|
+
this.maybeStartGlobalTimeout();
|
|
304
|
+
this.maybeArmStuckWatchdog();
|
|
305
|
+
if (isGlassesConnected(store.connection)) {
|
|
306
|
+
if (this.otaStartOwnership?.outcome === "pending") {
|
|
307
|
+
void this.sendOtaStartWithWatchdogs();
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
void BluetoothSdk.queryOtaStatus().catch(() => { });
|
|
311
|
+
this.armQueryReplyFallback("retry");
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
console.log("[OTA_PROGRESS] retry pressed after a rejected or terminal attempt — starting a fresh ota_start");
|
|
190
317
|
// Batch like the old event handler: React ran the whole handler (including
|
|
191
318
|
// the ota_start send) before re-rendering + re-running effects.
|
|
192
319
|
const wasReacting = this.reacting;
|
|
@@ -195,16 +322,17 @@ class OtaInstallCoordinator {
|
|
|
195
322
|
this.clearPerStepTimers();
|
|
196
323
|
this.clearLegacyApkSettleTimer();
|
|
197
324
|
this.clearMtkSimulationTimers();
|
|
325
|
+
this.clearContinueLockoutTimer();
|
|
198
326
|
this.retryCount = 0;
|
|
199
|
-
this.
|
|
200
|
-
|
|
327
|
+
if (!this.prepareFreshOtaStartAttempt())
|
|
328
|
+
return;
|
|
329
|
+
this.resetBesRestartAttempt();
|
|
201
330
|
this.setSawReconnectEdge(false);
|
|
202
331
|
this.setErrorMsg("");
|
|
203
|
-
this.
|
|
332
|
+
this.setContinueButtonDisabled(false);
|
|
204
333
|
this.setLegacyApkSettleHold(false);
|
|
205
334
|
this.setMtkSimulatedPercent(null);
|
|
206
335
|
this.lastRealMtkProgress = 0;
|
|
207
|
-
const store = useGlassesStore.getState();
|
|
208
336
|
store.setOtaProgress(null);
|
|
209
337
|
store.setOtaStatus(null);
|
|
210
338
|
if (isGlassesConnected(useGlassesStore.getState().connection)) {
|
|
@@ -222,12 +350,17 @@ class OtaInstallCoordinator {
|
|
|
222
350
|
* stays in the screen): clear the selected update, and after an APK step
|
|
223
351
|
* clear the stale build number so the next check re-reads version_info.
|
|
224
352
|
*/
|
|
225
|
-
finish() {
|
|
353
|
+
async finish() {
|
|
354
|
+
if (this.otaStartOwnership?.outcome !== "pending") {
|
|
355
|
+
this.otaStartOwnership = null;
|
|
356
|
+
}
|
|
226
357
|
useGlassesStore.getState().setOtaUpdateAvailable(null);
|
|
227
358
|
if (this.apkStepSeen) {
|
|
228
|
-
BluetoothSdk.updateGlasses({ buildNumber: "" });
|
|
229
359
|
useGlassesStore.getState().setGlassesInfo({ buildNumber: "" });
|
|
230
360
|
}
|
|
361
|
+
// The post-install check needs internet again. Do not let callers leave the
|
|
362
|
+
// terminal screen while Android still owns the no-internet hotspot route.
|
|
363
|
+
await this.teardownHotspotTransport();
|
|
231
364
|
}
|
|
232
365
|
/**
|
|
233
366
|
* Abandon an interrupted session (the super-mode skip): terminal cleanup
|
|
@@ -235,8 +368,8 @@ class OtaInstallCoordinator {
|
|
|
235
368
|
* deriveDisplayState doesn't resurrect the stale install when the host
|
|
236
369
|
* navigates away and back through /ota routes.
|
|
237
370
|
*/
|
|
238
|
-
discard() {
|
|
239
|
-
this.finish();
|
|
371
|
+
async discard() {
|
|
372
|
+
await this.finish();
|
|
240
373
|
const store = useGlassesStore.getState();
|
|
241
374
|
store.setOtaStatus(null);
|
|
242
375
|
store.setOtaProgress(null);
|
|
@@ -255,16 +388,44 @@ class OtaInstallCoordinator {
|
|
|
255
388
|
sawReconnectEdge: this.sawReconnectEdge,
|
|
256
389
|
legacyApkSettleHold: this.legacyApkSettleHold,
|
|
257
390
|
apkCompletedViaBuildIncrease: this.apkCompletedViaBuildIncrease,
|
|
391
|
+
besRestartRecovery: this.besRestartRecovery,
|
|
392
|
+
versionChangeConverged: this.versionChangeConverged,
|
|
393
|
+
versionChangeSession: this.versionChangeSession,
|
|
258
394
|
}),
|
|
259
395
|
errorMsg: this.errorMsg,
|
|
260
|
-
|
|
396
|
+
// Keep Continue disabled while the expected BES reboot is still in flight.
|
|
397
|
+
continueButtonDisabled: this.continueButtonDisabled || this.besRestartRecovery === "awaiting",
|
|
261
398
|
connected,
|
|
262
399
|
// Copies: the snapshot must not hand callers mutable references into the store.
|
|
263
400
|
otaStatus: otaStatus ? { ...otaStatus } : null,
|
|
264
401
|
otaProgress: otaProgress ? { ...otaProgress } : null,
|
|
265
402
|
mtkInstallStallSimulatedPercent: this.mtkSimulatedPercent,
|
|
403
|
+
isVersionChange: this.versionChangeSession,
|
|
404
|
+
versionChangeConverged: this.versionChangeConverged,
|
|
405
|
+
versionChangePhase: this.deriveVersionChangePhase(connected),
|
|
406
|
+
hotspotPhase: this.hotspotPhase,
|
|
407
|
+
hotspotArtifactPercent: this.hotspotArtifactPercent,
|
|
408
|
+
transport: this.selectedTransport,
|
|
266
409
|
};
|
|
267
410
|
}
|
|
411
|
+
/**
|
|
412
|
+
* True while the recovery worker owns the downgrade transaction (after the apk install
|
|
413
|
+
* started, before convergence). During this window ASG is being uninstalled/reinstalled and
|
|
414
|
+
* cannot answer the phone, so the coordinator must not ping, poll ota_query_status, or arm the
|
|
415
|
+
* progress-stall watchdog — doing so drives the passive factory build and fails the session
|
|
416
|
+
* before the target reconnects. Completion is exact-version convergence, detected separately.
|
|
417
|
+
*/
|
|
418
|
+
isInVersionChangeDetour() {
|
|
419
|
+
return this.versionChangeSession && this.versionChangeInstallStarted && !this.versionChangeConverged;
|
|
420
|
+
}
|
|
421
|
+
/** Downgrade-detour sub-phase for the progress narrative (see OtaInstallSnapshot). */
|
|
422
|
+
deriveVersionChangePhase(connected) {
|
|
423
|
+
if (!this.versionChangeSession || this.versionChangeConverged)
|
|
424
|
+
return null;
|
|
425
|
+
if (!this.versionChangeInstallStarted)
|
|
426
|
+
return "installing";
|
|
427
|
+
return connected ? "verifying" : "restarting";
|
|
428
|
+
}
|
|
268
429
|
/**
|
|
269
430
|
* Subscribe to install snapshot changes, deduped on the projected JSON.
|
|
270
431
|
* Fires on glasses-store changes AND coordinator-internal state changes
|
|
@@ -297,10 +458,16 @@ class OtaInstallCoordinator {
|
|
|
297
458
|
this.initialBuildNumber = null;
|
|
298
459
|
this.apkExpectedInSession = false;
|
|
299
460
|
this.apkCompletedViaBuildIncrease = false;
|
|
461
|
+
this.protocolProfile = null;
|
|
462
|
+
this.resetBesRestartAttempt();
|
|
463
|
+
this.versionChangeSession = false;
|
|
464
|
+
this.versionChangeTarget = null;
|
|
465
|
+
this.versionChangeInstallStarted = false;
|
|
466
|
+
this.versionChangeConverged = false;
|
|
300
467
|
this.legacyApkSettleHold = false;
|
|
301
468
|
this.mtkSimulatedPercent = null;
|
|
302
469
|
this.lastRealMtkProgress = 0;
|
|
303
|
-
this.
|
|
470
|
+
this.apkInstallPollInFlight = false;
|
|
304
471
|
this.hasFirstActivity = false;
|
|
305
472
|
this.hasFirstNonZeroProgress = false;
|
|
306
473
|
this.retryCount = 0;
|
|
@@ -314,6 +481,24 @@ class OtaInstallCoordinator {
|
|
|
314
481
|
this.lastStallSig = "";
|
|
315
482
|
this.reactQueued = false;
|
|
316
483
|
}
|
|
484
|
+
/** Begin a genuinely new command attempt only after native ownership has settled. */
|
|
485
|
+
prepareFreshOtaStartAttempt() {
|
|
486
|
+
if (this.otaStartOwnership?.outcome === "pending") {
|
|
487
|
+
console.error("[OTA_PROGRESS] refused fresh ota_start while native still owns the previous request");
|
|
488
|
+
return false;
|
|
489
|
+
}
|
|
490
|
+
this.otaStartOwnership = null;
|
|
491
|
+
this.hasFirstActivity = false;
|
|
492
|
+
this.hasFirstNonZeroProgress = false;
|
|
493
|
+
this.setApkCompletedViaBuildIncrease(false);
|
|
494
|
+
return true;
|
|
495
|
+
}
|
|
496
|
+
/** Reset only the reboot lifecycle that belongs to one install attempt. */
|
|
497
|
+
resetBesRestartAttempt() {
|
|
498
|
+
this.besRestartExpected = false;
|
|
499
|
+
this.besRestartDisconnectObserved = false;
|
|
500
|
+
this.besRestartRecovery = null;
|
|
501
|
+
}
|
|
317
502
|
/** Notify snapshot subscribers of a coordinator-internal state change. */
|
|
318
503
|
emitInternalChange() {
|
|
319
504
|
for (const check of Array.from(this.snapshotCheckers))
|
|
@@ -363,14 +548,18 @@ class OtaInstallCoordinator {
|
|
|
363
548
|
this.mtkSimulatedPercent = next;
|
|
364
549
|
this.emitInternalChange();
|
|
365
550
|
}
|
|
366
|
-
/**
|
|
367
|
-
* Legacy-session shape (WP 8C): event shape wins once events exist; before any event
|
|
368
|
-
* only the session-start build number can tell (falls back to the live build number
|
|
369
|
-
* until it is captured). Old builds get the LEGACY_* padded policy durations.
|
|
370
|
-
*/
|
|
551
|
+
/** Legacy/unified policy is selected once, matching the old sticky route choice. */
|
|
371
552
|
isLegacySessionShapeNow() {
|
|
553
|
+
if (this.protocolProfile)
|
|
554
|
+
return this.protocolProfile === "legacy";
|
|
372
555
|
const state = useGlassesStore.getState();
|
|
373
|
-
|
|
556
|
+
const selected = selectOtaProtocolProfile(state.otaStatus, state.otaProgress, this.initialBuildNumber || state.buildNumber);
|
|
557
|
+
if (selected) {
|
|
558
|
+
this.protocolProfile = selected;
|
|
559
|
+
console.log(`[OTA_PROGRESS] protocol profile selected: ${selected}`);
|
|
560
|
+
return selected === "legacy";
|
|
561
|
+
}
|
|
562
|
+
return isLegacyShapedOtaSession(state.otaStatus, state.otaProgress, state.buildNumber);
|
|
374
563
|
}
|
|
375
564
|
react() {
|
|
376
565
|
if (!this.attached)
|
|
@@ -418,6 +607,18 @@ class OtaInstallCoordinator {
|
|
|
418
607
|
this.handleLegacyInstallCompleteEdge(otaStatus);
|
|
419
608
|
}
|
|
420
609
|
const legacySession = this.isLegacySessionShapeNow();
|
|
610
|
+
if (this.isBesRestartExpected(otaStatus, otaProgress, legacySession)) {
|
|
611
|
+
this.besRestartExpected = true;
|
|
612
|
+
}
|
|
613
|
+
// Port the legacy route's actual reboot invariant before deriving display
|
|
614
|
+
// state, so reconnecting with stale BES in_progress@100 never renders updating.
|
|
615
|
+
if (connectedChanged && !connected && this.besRestartExpected && this.besRestartRecovery === null) {
|
|
616
|
+
this.besRestartDisconnectObserved = true;
|
|
617
|
+
this.besRestartRecovery = "awaiting";
|
|
618
|
+
console.log("[OTA_PROGRESS] BES restart disconnect observed — awaiting reconnect verification");
|
|
619
|
+
}
|
|
620
|
+
if (this.besRestartRecovery === "awaiting" && connected)
|
|
621
|
+
this.completeBesRestartRecovery();
|
|
421
622
|
// Derived UI state — the glasses data IS the source of truth.
|
|
422
623
|
const displayState = deriveDisplayState({
|
|
423
624
|
otaStatus,
|
|
@@ -427,6 +628,9 @@ class OtaInstallCoordinator {
|
|
|
427
628
|
sawReconnectEdge: this.sawReconnectEdge,
|
|
428
629
|
legacyApkSettleHold: this.legacyApkSettleHold,
|
|
429
630
|
apkCompletedViaBuildIncrease: this.apkCompletedViaBuildIncrease,
|
|
631
|
+
besRestartRecovery: this.besRestartRecovery,
|
|
632
|
+
versionChangeConverged: this.versionChangeConverged,
|
|
633
|
+
versionChangeSession: this.versionChangeSession,
|
|
430
634
|
});
|
|
431
635
|
const stallSig = buildProgressStalenessSignature(otaStatus, otaProgress, displayState);
|
|
432
636
|
const stallDuration = progressTimeoutDurationMs(otaStatus, otaProgress, legacySession);
|
|
@@ -481,6 +685,62 @@ class OtaInstallCoordinator {
|
|
|
481
685
|
console.log("[OTA_PROGRESS] legacy mtk install complete — marking MTK updated this session");
|
|
482
686
|
useGlassesStore.getState().setMtkUpdatedThisSession(true);
|
|
483
687
|
}
|
|
688
|
+
// Version-change (downgrade) install started: once ASG reports the apk install
|
|
689
|
+
// phase, the handoff to the recovery worker has happened. From here the phone
|
|
690
|
+
// stops driving ota_start (see runConnectEdge) and waits for exact-version
|
|
691
|
+
// convergence — the wipe means no resumable ASG session and no build increase.
|
|
692
|
+
if (this.versionChangeSession &&
|
|
693
|
+
!this.versionChangeInstallStarted &&
|
|
694
|
+
// Only latch from a status that belongs to THIS session: an install/apk status is a
|
|
695
|
+
// response to our own acknowledged ota_start, so a leftover from a prior session
|
|
696
|
+
// sitting in the store at mount cannot prematurely enter the detour wait (which would
|
|
697
|
+
// suppress ota_start and hang the fresh downgrade until the global timeout).
|
|
698
|
+
this.otaStartOwnership?.outcome === "acknowledged" &&
|
|
699
|
+
otaStatus?.stepType === "apk" &&
|
|
700
|
+
otaStatus.phase === "install") {
|
|
701
|
+
console.log("[OTA_PROGRESS] version-change: apk install started — entering detour wait");
|
|
702
|
+
this.versionChangeInstallStarted = true;
|
|
703
|
+
this.emitInternalChange();
|
|
704
|
+
}
|
|
705
|
+
// Ownership refuted: ASG emits install/STARTED BEFORE handing off, so the latch above can
|
|
706
|
+
// be set even when no transaction ends up owning the detour. Recovery answers every
|
|
707
|
+
// handoff synchronously, so exactly three error codes prove non-ownership and release the
|
|
708
|
+
// latch: downgrade_handoff_refused (recovery's explicit verdict),
|
|
709
|
+
// downgrade_handoff_failed (no verdict at all — recovery dead/missing, so it never began),
|
|
710
|
+
// and downgrade_transaction_stalled (the long-stop past recovery's own stale give-up).
|
|
711
|
+
// An accepted-but-slow transaction emits NONE of these (acceptance cancels the short
|
|
712
|
+
// watchdog), so the latch is never released while a live worker owns the staged artifact
|
|
713
|
+
// — which recovery additionally claims by rename at acceptance.
|
|
714
|
+
if (this.versionChangeInstallStarted &&
|
|
715
|
+
!this.versionChangeConverged &&
|
|
716
|
+
otaStatus?.status === "failed" &&
|
|
717
|
+
(otaStatus.error === "downgrade_handoff_refused" ||
|
|
718
|
+
otaStatus.error === "downgrade_handoff_failed" ||
|
|
719
|
+
otaStatus.error === "downgrade_transaction_stalled")) {
|
|
720
|
+
console.log(`[OTA_PROGRESS] version-change: no transaction owns the detour (${otaStatus.error}) — releasing latch`);
|
|
721
|
+
this.versionChangeInstallStarted = false;
|
|
722
|
+
this.emitInternalChange();
|
|
723
|
+
}
|
|
724
|
+
// Version-change convergence: the reconnected glasses report exactly the pinned
|
|
725
|
+
// target. This is the ONLY completion signal for a downgrade (lower build, no
|
|
726
|
+
// increase, wiped session). Mirrors the legacy build-increase latch but by equality.
|
|
727
|
+
if (this.versionChangeSession &&
|
|
728
|
+
this.versionChangeInstallStarted &&
|
|
729
|
+
!this.versionChangeConverged &&
|
|
730
|
+
this.versionChangeTarget !== null &&
|
|
731
|
+
buildNumberChanged &&
|
|
732
|
+
buildNumber) {
|
|
733
|
+
const currentBuild = Number.parseInt(buildNumber, 10);
|
|
734
|
+
if (Number.isFinite(currentBuild) && currentBuild === this.versionChangeTarget) {
|
|
735
|
+
console.log(`[OTA_PROGRESS] version-change converged: glasses report target ${currentBuild}, completing`);
|
|
736
|
+
this.clearPerStepTimers();
|
|
737
|
+
this.clearGlobalTimeout();
|
|
738
|
+
this.apkStepSeen = true;
|
|
739
|
+
this.setErrorMsg("");
|
|
740
|
+
this.versionChangeConverged = true;
|
|
741
|
+
this.emitInternalChange();
|
|
742
|
+
}
|
|
743
|
+
}
|
|
484
744
|
// Legacy APK completion by build-number increase (WP 8C-c): old builds reboot into
|
|
485
745
|
// the new build without a reliable explicit reconnect status; the fresh version_info
|
|
486
746
|
// build number is the completion signal (ported from progress-legacy.tsx, which
|
|
@@ -516,7 +776,7 @@ class OtaInstallCoordinator {
|
|
|
516
776
|
}
|
|
517
777
|
/**
|
|
518
778
|
* Connect-edge — the only place that decides to send ota_start /
|
|
519
|
-
* ota_query_status
|
|
779
|
+
* ota_query_status and flips sawReconnectEdge
|
|
520
780
|
* on the false -> true transition that signals the BES reboot completed.
|
|
521
781
|
*/
|
|
522
782
|
if (connectedChanged) {
|
|
@@ -528,21 +788,23 @@ class OtaInstallCoordinator {
|
|
|
528
788
|
if (isMount) {
|
|
529
789
|
GlobalEventEmitter.on("ota_start_ack", this.handleAck);
|
|
530
790
|
GlobalEventEmitter.on("mtk_update_complete", this.handleMtkComplete);
|
|
791
|
+
GlobalEventEmitter.on("glasses_session_changed", this.handleGlassesSessionChanged);
|
|
531
792
|
}
|
|
532
793
|
// Ping keepalive while an OTA is actively running (legacy sessions pinged on the
|
|
533
794
|
// padded interval — old builds slept less eagerly and pings are costlier for them).
|
|
534
795
|
if (connectedChanged || displayStateChanged) {
|
|
535
796
|
this.clearPingInterval();
|
|
536
|
-
const active = connected && (displayState === "starting" || displayState === "updating");
|
|
797
|
+
const active = connected && (displayState === "starting" || displayState === "updating") && !this.isInVersionChangeDetour();
|
|
537
798
|
if (active) {
|
|
538
799
|
void BluetoothSdk.ping().catch(() => { });
|
|
539
800
|
const pingIntervalMs = legacySession ? LEGACY_PING_INTERVAL_MS : PING_INTERVAL_MS;
|
|
540
801
|
this.pingInterval = setInterval(() => {
|
|
541
802
|
void BluetoothSdk.ping().catch(() => { });
|
|
803
|
+
this.maybePollApkInstallStatus();
|
|
542
804
|
}, pingIntervalMs);
|
|
543
805
|
}
|
|
544
806
|
}
|
|
545
|
-
// Any glasses activity
|
|
807
|
+
// Any glasses activity proves the logical ota_start was accepted.
|
|
546
808
|
if ((otaStatusChanged || otaProgressChanged) && (otaStatus || otaProgress)) {
|
|
547
809
|
this.onFirstActivity();
|
|
548
810
|
}
|
|
@@ -563,7 +825,11 @@ class OtaInstallCoordinator {
|
|
|
563
825
|
// update that doesn't change the signature keeps the same timer running
|
|
564
826
|
// instead of forever re-extending the deadline.
|
|
565
827
|
if (stallSigChanged || displayStateChanged) {
|
|
566
|
-
if (
|
|
828
|
+
if (this.isInVersionChangeDetour()) {
|
|
829
|
+
// Recovery worker owns the transaction; there are no progress events to stall on.
|
|
830
|
+
this.clearProgressTimeout();
|
|
831
|
+
}
|
|
832
|
+
else if (displayState !== "starting" && displayState !== "updating") {
|
|
567
833
|
this.clearProgressTimeout();
|
|
568
834
|
}
|
|
569
835
|
else if (!stallSig) {
|
|
@@ -583,19 +849,26 @@ class OtaInstallCoordinator {
|
|
|
583
849
|
}
|
|
584
850
|
// Lockout on Continue button after BES restart to prevent accidental tap
|
|
585
851
|
// (15s unified, 35s for legacy-shaped sessions — WP 8C-f).
|
|
586
|
-
if (displayStateChanged) {
|
|
852
|
+
if (displayStateChanged && displayState === "restarting") {
|
|
587
853
|
this.clearContinueLockoutTimer();
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
}
|
|
854
|
+
this.clearRestartRecoveryTimeout();
|
|
855
|
+
const lockoutMs = legacySession ? LEGACY_BES_CONTINUE_LOCKOUT_MS : BES_CONTINUE_LOCKOUT_MS;
|
|
856
|
+
const restartTimeoutMs = legacySession ? LEGACY_BES_RESTART_TIMEOUT_MS : BES_RESTART_TIMEOUT_MS;
|
|
857
|
+
this.setContinueButtonDisabled(true);
|
|
858
|
+
this.continueLockoutTimer = setTimeout(() => {
|
|
859
|
+
this.continueLockoutTimer = null;
|
|
860
|
+
this.setContinueButtonDisabled(false);
|
|
861
|
+
}, lockoutMs);
|
|
862
|
+
this.restartRecoveryTimeout = setTimeout(() => {
|
|
863
|
+
this.restartRecoveryTimeout = null;
|
|
864
|
+
if (this.computeDisplayStateNow() !== "restarting")
|
|
865
|
+
return;
|
|
866
|
+
console.log(`[OTA_PROGRESS] watchdog: glasses did not finish restarting in ${restartTimeoutMs}ms`);
|
|
867
|
+
this.setErrorMsg(OtaProgressMessages.restartTimeout);
|
|
868
|
+
}, restartTimeoutMs);
|
|
596
869
|
}
|
|
597
|
-
if (displayStateChanged
|
|
598
|
-
this.
|
|
870
|
+
else if (displayStateChanged) {
|
|
871
|
+
this.clearRestartRecoveryTimeout();
|
|
599
872
|
}
|
|
600
873
|
// Legacy MTK install stall simulation (WP 8C-e): display-only. The simulation
|
|
601
874
|
// timers only ever touch the projected snapshot percent — they never feed back
|
|
@@ -609,91 +882,128 @@ class OtaInstallCoordinator {
|
|
|
609
882
|
// completions (which never produce a `complete` ota_status).
|
|
610
883
|
if (displayStateChanged &&
|
|
611
884
|
(displayState === "complete" || displayState === "restarting" || displayState === "failed")) {
|
|
885
|
+
if (displayState === "failed") {
|
|
886
|
+
this.clearPerStepTimers();
|
|
887
|
+
}
|
|
612
888
|
useGlassesStore.getState().setOtaUpdateAvailable(null);
|
|
889
|
+
if (displayState === "complete" || displayState === "failed") {
|
|
890
|
+
void this.teardownHotspotTransport();
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* Reconnect arbitration shared by the physical BLE connect edge and the
|
|
896
|
+
* glasses_session_changed signal. The latter exists because the BES keeps the BLE
|
|
897
|
+
* link alive across asg_client restarts (APK OTA): no physical edge ever fires, so
|
|
898
|
+
* the changed process session id is the only reconnect signal the coordinator gets
|
|
899
|
+
* (incident rep_01KY6BJ0B7A4RBMQ7VN39KAE5E). Always returns true (the caller's
|
|
900
|
+
* edge handling is complete).
|
|
901
|
+
*/
|
|
902
|
+
runReconnectArbitration(label) {
|
|
903
|
+
console.log(`[OTA_PROGRESS] ${label}: false->true, flipping sawReconnectEdge=true`);
|
|
904
|
+
// A physical reconnect passed through the disconnect branch first, which cleared
|
|
905
|
+
// these timers; a session-change edge never disconnects, so a fallback armed by an
|
|
906
|
+
// earlier mount/query could otherwise fire alongside the arbitration below and send
|
|
907
|
+
// a duplicate ota_start. Clearing here is a no-op on the physical path.
|
|
908
|
+
this.clearQueryReplyTimeout();
|
|
909
|
+
this.setSawReconnectEdge(true);
|
|
910
|
+
if (this.besRestartDisconnectObserved) {
|
|
911
|
+
console.log(`[OTA_PROGRESS] ${label}: BES reboot reconnect observed — no ota_query_status`);
|
|
912
|
+
this.completeBesRestartRecovery();
|
|
913
|
+
return true;
|
|
914
|
+
}
|
|
915
|
+
// Legacy apk settle window (WP 8C): the glasses restarting into the new build is
|
|
916
|
+
// the EXPECTED reconnect here — the legacy screen took no action on it. Skip the
|
|
917
|
+
// query/fallback; the settle timer (or the build-number increase) completes.
|
|
918
|
+
if (this.legacyApkSettleHold) {
|
|
919
|
+
console.log(`[OTA_PROGRESS] ${label}: reconnect during legacy apk settle hold — no query`);
|
|
920
|
+
return true;
|
|
921
|
+
}
|
|
922
|
+
console.log(`[OTA_PROGRESS] ${label}: reconnected, sending ota_query_status`);
|
|
923
|
+
if (this.otaStartOwnership?.outcome === "pending") {
|
|
924
|
+
void this.sendOtaStartWithWatchdogs();
|
|
613
925
|
}
|
|
926
|
+
void BluetoothSdk.queryOtaStatus().catch(() => { });
|
|
927
|
+
this.armQueryReplyFallback("reconnect");
|
|
928
|
+
return true;
|
|
614
929
|
}
|
|
615
930
|
runConnectEdge(connected) {
|
|
616
931
|
const prev = this.prevConnected;
|
|
617
932
|
this.prevConnected = connected;
|
|
618
933
|
if (!connected) {
|
|
619
934
|
console.log("[OTA_PROGRESS] connect-edge: disconnected");
|
|
620
|
-
|
|
621
|
-
//
|
|
622
|
-
//
|
|
623
|
-
// re-queries and re-arms this fallback anyway.
|
|
935
|
+
// Also drop pending query/retry sends: firing ota_start into a dead link
|
|
936
|
+
// can only produce a false "failed" state. Reconnect arbitration first
|
|
937
|
+
// queries durable status and re-arms the one permitted fallback.
|
|
624
938
|
this.clearQueryReplyTimeout();
|
|
939
|
+
this.clearRetryTimeout();
|
|
625
940
|
return;
|
|
626
941
|
}
|
|
627
942
|
const becameConnected = prev === false && connected === true;
|
|
628
|
-
if (becameConnected) {
|
|
629
|
-
console.log("[OTA_PROGRESS] connect-edge: false->true, flipping sawReconnectEdge=true");
|
|
630
|
-
this.setSawReconnectEdge(true);
|
|
631
|
-
}
|
|
632
|
-
// Legacy apk settle window (WP 8C): the glasses restarting into the new build is
|
|
633
|
-
// the EXPECTED reconnect here — the legacy screen took no action on it. Skip the
|
|
634
|
-
// query/fallback; the settle timer (or the build-number increase) completes.
|
|
635
|
-
if (becameConnected && this.legacyApkSettleHold) {
|
|
636
|
-
console.log("[OTA_PROGRESS] connect-edge: reconnect during legacy apk settle hold — no query");
|
|
943
|
+
if (becameConnected && this.runReconnectArbitration("connect-edge")) {
|
|
637
944
|
return;
|
|
638
945
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
this.postApkDelay = null;
|
|
647
|
-
this.retryCount = 0;
|
|
648
|
-
this.hasFirstActivity = false;
|
|
649
|
-
this.hasFirstNonZeroProgress = false;
|
|
650
|
-
this.hasReceivedAck = false;
|
|
651
|
-
console.log("[OTA_PROGRESS] POST_APK delay fired, sending ota_start");
|
|
652
|
-
void this.sendOtaStartWithWatchdogs();
|
|
653
|
-
}, POST_APK_OTA_START_DELAY_MS);
|
|
654
|
-
return;
|
|
655
|
-
}
|
|
656
|
-
if (becameConnected) {
|
|
657
|
-
console.log("[OTA_PROGRESS] connect-edge: reconnected, sending ota_query_status");
|
|
658
|
-
void BluetoothSdk.sendOtaQueryStatus();
|
|
659
|
-
this.armQueryReplyFallback("reconnect");
|
|
946
|
+
// Version-change detour: after the install started, EVERY reconnect is expected
|
|
947
|
+
// (the passive factory build, then the target). Do NOT send ota_start/query — the
|
|
948
|
+
// factory build would treat the pin as a plain upgrade and start a redundant
|
|
949
|
+
// download racing the recovery worker. Convergence (buildNumber === target) is
|
|
950
|
+
// detected in the reaction pass; here we simply take no action.
|
|
951
|
+
if (this.versionChangeSession && this.versionChangeInstallStarted && !this.versionChangeConverged) {
|
|
952
|
+
console.log("[OTA_PROGRESS] connect-edge: reconnect during version-change detour — no query (expected)");
|
|
660
953
|
return;
|
|
661
954
|
}
|
|
955
|
+
const storeState = useGlassesStore.getState();
|
|
662
956
|
// Initial mount (prev === current === true). If no session yet, kick off ota_start.
|
|
663
957
|
// An idle ota_status has an empty sessionId; treat it as no session so ota_start
|
|
664
958
|
// still begins an explicit install.
|
|
665
959
|
const isIdleStatus = !!storeState.otaStatus && storeState.otaStatus.sessionId === "";
|
|
666
960
|
const noSessionYet = (!storeState.otaStatus && !storeState.otaProgress) || isIdleStatus;
|
|
667
961
|
if (noSessionYet) {
|
|
962
|
+
if (!isIdleStatus && this.otaStartOwnership?.outcome === "acknowledged") {
|
|
963
|
+
console.log("[OTA_PROGRESS] initial mount, acknowledged session has no cached status — reconciling");
|
|
964
|
+
void BluetoothSdk.queryOtaStatus().catch(() => { });
|
|
965
|
+
this.armQueryReplyFallback("initial-mount");
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
668
968
|
console.log(isIdleStatus
|
|
669
969
|
? "[OTA_PROGRESS] initial mount, idle status (no sessionId), sending ota_start"
|
|
670
970
|
: "[OTA_PROGRESS] initial mount, no session in store, sending ota_start");
|
|
671
971
|
this.retryCount = 0;
|
|
672
|
-
this.
|
|
673
|
-
|
|
674
|
-
|
|
972
|
+
if (isIdleStatus && this.otaStartOwnership?.outcome !== "pending" && !this.prepareFreshOtaStartAttempt()) {
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
675
975
|
void this.sendOtaStartWithWatchdogs();
|
|
676
976
|
}
|
|
677
977
|
else {
|
|
678
978
|
console.log("[OTA_PROGRESS] initial mount, session exists, sending ota_query_status");
|
|
679
|
-
void BluetoothSdk.
|
|
979
|
+
void BluetoothSdk.queryOtaStatus().catch(() => { });
|
|
680
980
|
this.armQueryReplyFallback("initial-mount");
|
|
681
981
|
}
|
|
682
982
|
}
|
|
683
983
|
// --- BLE OTA event listeners (engine GlobalEventEmitter, fed by OtaService) ---
|
|
684
984
|
handleAck = () => {
|
|
685
|
-
|
|
985
|
+
const ownership = this.otaStartOwnership;
|
|
986
|
+
if (!ownership || ownership.outcome === "rejected")
|
|
987
|
+
return;
|
|
988
|
+
if (ownership.outcome === "acknowledged")
|
|
686
989
|
return;
|
|
687
990
|
console.log("[OTA_PROGRESS] ota_start_ack received");
|
|
688
|
-
|
|
991
|
+
ownership.outcome = "acknowledged";
|
|
689
992
|
this.clearRetryTimeout();
|
|
690
993
|
};
|
|
994
|
+
/**
|
|
995
|
+
* The asg process restarted while the BES kept the BLE link up (sid change observed
|
|
996
|
+
* by the bridge): treat it exactly like a physical reconnect edge.
|
|
997
|
+
*/
|
|
998
|
+
handleGlassesSessionChanged = () => {
|
|
999
|
+
this.runReconnectArbitration("session-change");
|
|
1000
|
+
};
|
|
691
1001
|
handleMtkComplete = () => {
|
|
692
1002
|
console.log("[OTA_PROGRESS] mtk_update_complete received");
|
|
693
1003
|
this.clearProgressTimeout();
|
|
694
1004
|
this.onFirstActivity();
|
|
695
1005
|
this.onFirstNonZeroProgress();
|
|
696
|
-
void BluetoothSdk.
|
|
1006
|
+
void BluetoothSdk.queryOtaStatus().catch(() => { });
|
|
697
1007
|
useGlassesStore.getState().setMtkUpdatedThisSession(true);
|
|
698
1008
|
};
|
|
699
1009
|
// --- legacy (< 37) compatibility, ported from progress-legacy.tsx (WP 8C) ---
|
|
@@ -732,6 +1042,34 @@ class OtaInstallCoordinator {
|
|
|
732
1042
|
}, LEGACY_APK_COMPLETION_SETTLE_MS);
|
|
733
1043
|
}
|
|
734
1044
|
}
|
|
1045
|
+
isBesRestartExpected(otaStatus, otaProgress, legacySession) {
|
|
1046
|
+
const statusReadyToRestart = otaStatus?.stepType === "bes" &&
|
|
1047
|
+
otaStatus.phase === "install" &&
|
|
1048
|
+
(otaStatus.status === "step_complete" || otaStatus.status === "complete");
|
|
1049
|
+
const progressReadyToRestart = legacySession &&
|
|
1050
|
+
otaProgress?.currentUpdate === "bes" &&
|
|
1051
|
+
otaProgress.stage === "install" &&
|
|
1052
|
+
otaProgress.status === "FINISHED";
|
|
1053
|
+
// progress-legacy treated a BES disconnect during active installation as the
|
|
1054
|
+
// expected power cycle even when its terminal FINISHED event was lost. Keep
|
|
1055
|
+
// that fallback only for the sticky pre-37 profile selected when the flow began.
|
|
1056
|
+
const legacyInstallInProgress = legacySession &&
|
|
1057
|
+
((otaStatus?.stepType === "bes" && otaStatus.phase === "install" && otaStatus.status === "in_progress") ||
|
|
1058
|
+
(otaProgress?.currentUpdate === "bes" &&
|
|
1059
|
+
otaProgress.stage === "install" &&
|
|
1060
|
+
(otaProgress.status === "STARTED" || otaProgress.status === "PROGRESS")));
|
|
1061
|
+
return statusReadyToRestart || progressReadyToRestart || legacyInstallInProgress;
|
|
1062
|
+
}
|
|
1063
|
+
/** Complete only after a protocol-authorized BES restart traverses disconnect -> reconnect. */
|
|
1064
|
+
completeBesRestartRecovery() {
|
|
1065
|
+
if (!this.besRestartDisconnectObserved || this.besRestartRecovery !== "awaiting")
|
|
1066
|
+
return;
|
|
1067
|
+
console.log("[OTA_PROGRESS] BES restart reconnected — completing by reboot edge");
|
|
1068
|
+
this.clearPerStepTimers();
|
|
1069
|
+
this.setErrorMsg("");
|
|
1070
|
+
this.besRestartRecovery = "complete";
|
|
1071
|
+
this.emitInternalChange();
|
|
1072
|
+
}
|
|
735
1073
|
/**
|
|
736
1074
|
* Legacy MTK install stall simulation (WP 8C-e), ported verbatim: the MTK system
|
|
737
1075
|
* install typically stalls around 49-50%. Every real event restarts stall detection;
|
|
@@ -795,10 +1133,45 @@ class OtaInstallCoordinator {
|
|
|
795
1133
|
sawReconnectEdge: this.sawReconnectEdge,
|
|
796
1134
|
legacyApkSettleHold: this.legacyApkSettleHold,
|
|
797
1135
|
apkCompletedViaBuildIncrease: this.apkCompletedViaBuildIncrease,
|
|
1136
|
+
besRestartRecovery: this.besRestartRecovery,
|
|
1137
|
+
versionChangeConverged: this.versionChangeConverged,
|
|
1138
|
+
versionChangeSession: this.versionChangeSession,
|
|
798
1139
|
});
|
|
799
1140
|
}
|
|
800
1141
|
/**
|
|
801
|
-
*
|
|
1142
|
+
* During an APK install the package installer kills and restarts the glasses
|
|
1143
|
+
* process, and the new process's completion push can lose its startup race
|
|
1144
|
+
* against the UART transport (incident rep_01KY31HEMTSBSMK8DVMNXJ5XGG: the
|
|
1145
|
+
* phone sat on a stale "install in_progress 0%" until the stall watchdog
|
|
1146
|
+
* failed a successful update). The persisted session on the glasses always
|
|
1147
|
+
* knows the truth, so poll ota_query_status on the keepalive tick while an
|
|
1148
|
+
* apk step sits in its install phase — the reply either confirms progress or
|
|
1149
|
+
* delivers the missed completion. Gated to the apk install phase so a stray
|
|
1150
|
+
* idle reply can't disturb an active download; legacy (< 37) builds ignore
|
|
1151
|
+
* the query, which is harmless.
|
|
1152
|
+
*
|
|
1153
|
+
* The native query pends up to 15s and rejects a concurrent call with
|
|
1154
|
+
* request_in_flight — longer than the 10s tick — so exactly one poll is kept
|
|
1155
|
+
* in flight (the glasses being slow to answer IS the silent-restart window
|
|
1156
|
+
* this poll exists for) and rejections are swallowed: the next tick retries.
|
|
1157
|
+
*/
|
|
1158
|
+
maybePollApkInstallStatus() {
|
|
1159
|
+
if (this.isInVersionChangeDetour())
|
|
1160
|
+
return;
|
|
1161
|
+
if (this.apkInstallPollInFlight)
|
|
1162
|
+
return;
|
|
1163
|
+
const s = useGlassesStore.getState().otaStatus;
|
|
1164
|
+
if (s?.stepType === "apk" && s.phase === "install" && s.status === "in_progress") {
|
|
1165
|
+
this.apkInstallPollInFlight = true;
|
|
1166
|
+
void BluetoothSdk.queryOtaStatus()
|
|
1167
|
+
.catch(() => { })
|
|
1168
|
+
.finally(() => {
|
|
1169
|
+
this.apkInstallPollInFlight = false;
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
/**
|
|
1174
|
+
* "Glasses are talking to us" — suppresses retry if the native request later rejects.
|
|
802
1175
|
* Important: this does NOT clear the stuck-at-zero watchdog; that one fires
|
|
803
1176
|
* on real progress > 0% (see {@link onFirstNonZeroProgress}).
|
|
804
1177
|
*/
|
|
@@ -806,6 +1179,9 @@ class OtaInstallCoordinator {
|
|
|
806
1179
|
if (this.hasFirstActivity)
|
|
807
1180
|
return;
|
|
808
1181
|
this.hasFirstActivity = true;
|
|
1182
|
+
if (this.otaStartOwnership?.outcome === "pending") {
|
|
1183
|
+
this.otaStartOwnership.outcome = "acknowledged";
|
|
1184
|
+
}
|
|
809
1185
|
this.clearRetryTimeout();
|
|
810
1186
|
}
|
|
811
1187
|
/**
|
|
@@ -828,7 +1204,7 @@ class OtaInstallCoordinator {
|
|
|
828
1204
|
// Legacy-shape check happens once, when the session cap is armed at the first
|
|
829
1205
|
// ota_start send (WP 8C-b): old builds get the padded legacy cap for the whole
|
|
830
1206
|
// session; a session that starts unified keeps the unified cap.
|
|
831
|
-
const globalTimeoutMs = this.isLegacySessionShapeNow() ? LEGACY_GLOBAL_OTA_TIMEOUT_MS : GLOBAL_OTA_TIMEOUT_MS;
|
|
1207
|
+
const globalTimeoutMs = this.versionChangeSession || this.isLegacySessionShapeNow() ? LEGACY_GLOBAL_OTA_TIMEOUT_MS : GLOBAL_OTA_TIMEOUT_MS;
|
|
832
1208
|
this.globalTimeout = setTimeout(() => {
|
|
833
1209
|
this.globalTimeout = null;
|
|
834
1210
|
this.globalTimeoutStarted = false;
|
|
@@ -844,35 +1220,12 @@ class OtaInstallCoordinator {
|
|
|
844
1220
|
this.setErrorMsg(OtaProgressMessages.globalTimeout);
|
|
845
1221
|
}, globalTimeoutMs);
|
|
846
1222
|
}
|
|
847
|
-
|
|
848
|
-
this.clearRetryTimeout();
|
|
1223
|
+
armStuckWatchdogOnly() {
|
|
849
1224
|
this.clearStuckTimeout();
|
|
850
1225
|
// Durations resolved at arm time (WP 8C-b): legacy-shaped sessions get the
|
|
851
1226
|
// padded values progress-legacy.tsx used for old (< 37) builds.
|
|
852
1227
|
const legacySession = this.isLegacySessionShapeNow();
|
|
853
|
-
const retryIntervalMs = legacySession ? LEGACY_RETRY_INTERVAL_MS : RETRY_INTERVAL_MS;
|
|
854
1228
|
const stuckTimeoutMs = legacySession ? LEGACY_DOWNLOAD_STUCK_TIMEOUT_MS : DOWNLOAD_STUCK_TIMEOUT_MS;
|
|
855
|
-
this.retryTimeout = setTimeout(() => {
|
|
856
|
-
this.retryTimeout = null;
|
|
857
|
-
if (this.hasReceivedAck || this.hasFirstActivity)
|
|
858
|
-
return;
|
|
859
|
-
if (this.computeDisplayStateNow() !== "starting")
|
|
860
|
-
return;
|
|
861
|
-
if (this.retryCount < MAX_RETRIES - 1) {
|
|
862
|
-
this.retryCount += 1;
|
|
863
|
-
this.hasReceivedAck = false;
|
|
864
|
-
console.log(`[OTA_PROGRESS] watchdog: no ack in ${retryIntervalMs}ms, retrying ota_start (attempt ${this.retryCount})`);
|
|
865
|
-
void this.sendOtaStartWithWatchdogs(true)
|
|
866
|
-
.then(() => {
|
|
867
|
-
this.armAckAndStuckWatchdogsOnly();
|
|
868
|
-
})
|
|
869
|
-
.catch(() => { });
|
|
870
|
-
}
|
|
871
|
-
else {
|
|
872
|
-
console.log(`[OTA_PROGRESS] watchdog: ota_start ack never received after ${MAX_RETRIES} attempts, failing`);
|
|
873
|
-
this.setErrorMsg(OtaProgressMessages.noAckResponse);
|
|
874
|
-
}
|
|
875
|
-
}, retryIntervalMs);
|
|
876
1229
|
this.stuckTimeout = setTimeout(() => {
|
|
877
1230
|
this.stuckTimeout = null;
|
|
878
1231
|
const d = this.computeDisplayStateNow();
|
|
@@ -888,27 +1241,123 @@ class OtaInstallCoordinator {
|
|
|
888
1241
|
this.setErrorMsg(OtaProgressMessages.stalledOrStuck);
|
|
889
1242
|
}, stuckTimeoutMs);
|
|
890
1243
|
}
|
|
891
|
-
|
|
1244
|
+
/** Arm the 0%-stuck watchdog only when this logical session has none. */
|
|
1245
|
+
maybeArmStuckWatchdog() {
|
|
1246
|
+
if (this.stuckTimeout || this.hasFirstNonZeroProgress)
|
|
1247
|
+
return;
|
|
1248
|
+
this.armStuckWatchdogOnly();
|
|
1249
|
+
}
|
|
1250
|
+
sendOtaStartWithWatchdogs() {
|
|
1251
|
+
// INVARIANT BACKSTOP — not normal control flow. Every entry point that can drive the
|
|
1252
|
+
// glasses (connect-edge, query fallback, retry, mount) carries its own detour gate with the
|
|
1253
|
+
// right behavior for that site; this final check only exists so that a FUTURE entry point
|
|
1254
|
+
// that forgets its gate degrades to a refused send + loud log instead of a corrupted
|
|
1255
|
+
// transaction (an ota_start mid-detour starts a parallel install on the factory build and
|
|
1256
|
+
// can trigger a second handoff). If this ever fires, a missed site gate exists — fix it.
|
|
1257
|
+
if (this.isInVersionChangeDetour()) {
|
|
1258
|
+
console.error("[OTA_PROGRESS] BACKSTOP: refused ota_start during a latched version-change detour — a caller is missing its detour gate");
|
|
1259
|
+
return Promise.resolve();
|
|
1260
|
+
}
|
|
1261
|
+
const existingOwnership = this.otaStartOwnership;
|
|
1262
|
+
if (existingOwnership?.outcome === "pending") {
|
|
1263
|
+
this.maybeStartGlobalTimeout();
|
|
1264
|
+
if (this.selectedTransport === "wifi" || this.hotspotManifestUrl)
|
|
1265
|
+
this.maybeArmStuckWatchdog();
|
|
1266
|
+
console.log("[OTA_PROGRESS] ota_start already pending — adopting existing native request");
|
|
1267
|
+
return existingOwnership.promise;
|
|
1268
|
+
}
|
|
1269
|
+
if (existingOwnership?.outcome === "acknowledged") {
|
|
1270
|
+
this.maybeStartGlobalTimeout();
|
|
1271
|
+
this.maybeArmStuckWatchdog();
|
|
1272
|
+
console.log("[OTA_PROGRESS] ota_start already acknowledged — refusing duplicate start");
|
|
1273
|
+
return Promise.resolve();
|
|
1274
|
+
}
|
|
1275
|
+
if (existingOwnership?.outcome === "rejected" && !this.prepareFreshOtaStartAttempt()) {
|
|
1276
|
+
return Promise.resolve();
|
|
1277
|
+
}
|
|
892
1278
|
this.maybeStartGlobalTimeout();
|
|
893
|
-
this.
|
|
894
|
-
|
|
1279
|
+
if (this.selectedTransport === "wifi" || this.hotspotManifestUrl)
|
|
1280
|
+
this.maybeArmStuckWatchdog();
|
|
1281
|
+
const ownership = {
|
|
1282
|
+
outcome: "pending",
|
|
1283
|
+
promise: Promise.resolve(),
|
|
1284
|
+
};
|
|
1285
|
+
ownership.promise = this.performOtaStart(ownership);
|
|
1286
|
+
this.otaStartOwnership = ownership;
|
|
1287
|
+
return ownership.promise;
|
|
1288
|
+
}
|
|
1289
|
+
async performOtaStart(ownership) {
|
|
1290
|
+
let nativeStartAttempted = false;
|
|
895
1291
|
try {
|
|
896
1292
|
const state = useGlassesStore.getState();
|
|
897
|
-
|
|
898
|
-
|
|
1293
|
+
let otaVersionUrl = resolveOtaManifestUrl(state.otaVersionUrl, state.buildNumber);
|
|
1294
|
+
if (this.selectedTransport === "hotspot") {
|
|
1295
|
+
if (!this.hotspotManifestUrl) {
|
|
1296
|
+
if (!this.preparedCheckResult) {
|
|
1297
|
+
throw new Error("No selected OTA check is available for hotspot staging");
|
|
1298
|
+
}
|
|
1299
|
+
this.hotspotManifestUrl = await hotspotOtaTransport.prepare(this.preparedCheckResult, (progress) => {
|
|
1300
|
+
this.hotspotPhase = progress.phase;
|
|
1301
|
+
this.hotspotArtifactPercent = progress.artifact?.artifactPercent ?? null;
|
|
1302
|
+
this.emitInternalChange();
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1305
|
+
otaVersionUrl = this.hotspotManifestUrl;
|
|
1306
|
+
this.maybeArmStuckWatchdog();
|
|
1307
|
+
}
|
|
1308
|
+
if (!otaVersionUrl) {
|
|
1309
|
+
throw new Error("OTA is disabled because this build has no immutable manifest pin");
|
|
1310
|
+
}
|
|
1311
|
+
console.log(`[OTA_PROGRESS] sending ota_start with ${this.selectedTransport} manifest URL: ${otaVersionUrl}`);
|
|
1312
|
+
nativeStartAttempted = true;
|
|
899
1313
|
await BluetoothSdk.startOtaUpdate(otaVersionUrl);
|
|
1314
|
+
if (this.otaStartOwnership !== ownership)
|
|
1315
|
+
return;
|
|
1316
|
+
// The public SDK promise resolves only from ota_start_ack. Treat that
|
|
1317
|
+
// resolution as the acknowledgement even if the parallel event dispatch
|
|
1318
|
+
// reaches the coordinator later (or is dropped by a remount).
|
|
1319
|
+
this.handleAck();
|
|
900
1320
|
}
|
|
901
1321
|
catch (err) {
|
|
902
1322
|
console.warn("[OTA_PROGRESS] sendOtaStart threw", err);
|
|
903
|
-
this.
|
|
1323
|
+
if (this.otaStartOwnership !== ownership)
|
|
1324
|
+
return;
|
|
1325
|
+
if (this.selectedTransport === "hotspot" && !nativeStartAttempted) {
|
|
1326
|
+
ownership.outcome = "rejected";
|
|
1327
|
+
this.clearStuckTimeout();
|
|
1328
|
+
await this.teardownHotspotTransport();
|
|
1329
|
+
if (this.attached)
|
|
1330
|
+
this.setErrorMsg(hotspotPreflightErrorMessage(err));
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
// A received ota_status/legacy progress event, or the exact legacy APK
|
|
1334
|
+
// build-number proof, proves that this logical start was accepted. Its
|
|
1335
|
+
// application-level ack may be lost while the update continues; never
|
|
1336
|
+
// create another transaction or fail a live, restarting, or completed
|
|
1337
|
+
// session because that stale promise timed out.
|
|
1338
|
+
if (this.hasFirstActivity || this.apkCompletedViaBuildIncrease) {
|
|
1339
|
+
ownership.outcome = "acknowledged";
|
|
1340
|
+
console.log("[OTA_PROGRESS] ota_start rejection arrived after acceptance proof — ignoring stale rejection");
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1343
|
+
ownership.outcome = "rejected";
|
|
1344
|
+
if (!this.attached)
|
|
1345
|
+
return;
|
|
904
1346
|
this.clearStuckTimeout();
|
|
1347
|
+
if (!isGlassesConnected(useGlassesStore.getState().connection)) {
|
|
1348
|
+
console.log("[OTA_PROGRESS] ota_start request ended while disconnected; deferring retry to reconnect arbitration");
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
905
1351
|
if (this.retryCount < MAX_RETRIES - 1) {
|
|
906
|
-
|
|
907
|
-
this.retryCount += 1;
|
|
908
|
-
}
|
|
1352
|
+
this.retryCount += 1;
|
|
909
1353
|
const retryIntervalMs = this.isLegacySessionShapeNow() ? LEGACY_RETRY_INTERVAL_MS : RETRY_INTERVAL_MS;
|
|
1354
|
+
console.log(`[OTA_PROGRESS] ota_start request ended without ack or activity; retrying after ${retryIntervalMs}ms (attempt ${this.retryCount + 1}/${MAX_RETRIES})`);
|
|
910
1355
|
this.retryTimeout = setTimeout(() => {
|
|
911
1356
|
this.retryTimeout = null;
|
|
1357
|
+
if (!isGlassesConnected(useGlassesStore.getState().connection)) {
|
|
1358
|
+
console.log("[OTA_PROGRESS] ota_start retry paused while disconnected; reconnect arbitration will resume");
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
912
1361
|
void this.sendOtaStartWithWatchdogs();
|
|
913
1362
|
}, retryIntervalMs);
|
|
914
1363
|
}
|
|
@@ -918,12 +1367,22 @@ class OtaInstallCoordinator {
|
|
|
918
1367
|
}
|
|
919
1368
|
}
|
|
920
1369
|
}
|
|
1370
|
+
async teardownHotspotTransport() {
|
|
1371
|
+
if (this.selectedTransport !== "hotspot" && !this.hotspotManifestUrl)
|
|
1372
|
+
return;
|
|
1373
|
+
await hotspotOtaTransport.teardown();
|
|
1374
|
+
this.hotspotManifestUrl = null;
|
|
1375
|
+
this.hotspotPhase = "idle";
|
|
1376
|
+
this.hotspotArtifactPercent = null;
|
|
1377
|
+
this.emitInternalChange();
|
|
1378
|
+
}
|
|
921
1379
|
/**
|
|
922
1380
|
* After sending ota_query_status, wait QUERY_REPLY_TIMEOUT_MS for the glasses
|
|
923
|
-
* to reply with a useful ota_status.
|
|
924
|
-
*
|
|
925
|
-
*
|
|
926
|
-
*
|
|
1381
|
+
* to reply with a useful ota_status. A unified session may start again only
|
|
1382
|
+
* after an explicit "idle" reply, or after a locally rejected ota_start has
|
|
1383
|
+
* produced no activity and no authoritative reply across reconnect. Legacy
|
|
1384
|
+
* builds ignore ota_query_status, so their established timeout fallback
|
|
1385
|
+
* remains the compatibility path.
|
|
927
1386
|
*
|
|
928
1387
|
* Cleared as soon as a non-idle otaStatus or any otaProgress lands in the
|
|
929
1388
|
* store (see the reaction block above). An idle reply intentionally does NOT
|
|
@@ -940,17 +1399,27 @@ class OtaInstallCoordinator {
|
|
|
940
1399
|
// whatever legacy-shaped status/progress sits in the store is a pre-query
|
|
941
1400
|
// leftover, not a reply — a NEW legacy event would have cleared this timer
|
|
942
1401
|
// through the reaction pass. Only a unified reply suppresses the fallback.
|
|
943
|
-
const legacySession =
|
|
1402
|
+
const legacySession = this.isLegacySessionShapeNow();
|
|
944
1403
|
if (!legacySession && hasRecoveringOtaReply(state.otaStatus, state.otaProgress))
|
|
945
1404
|
return;
|
|
1405
|
+
const explicitIdle = state.otaStatus?.status === "idle";
|
|
1406
|
+
const rejectedStart = this.otaStartOwnership?.outcome === "rejected";
|
|
1407
|
+
if (!legacySession && !explicitIdle && !rejectedStart) {
|
|
1408
|
+
console.log(`[OTA_PROGRESS] watchdog: ota_query_status got no authoritative idle reply in ${QUERY_REPLY_TIMEOUT_MS}ms (reason=${reason}); preserving the existing session`);
|
|
1409
|
+
return;
|
|
1410
|
+
}
|
|
946
1411
|
// Don't fire if we've left the active phase (e.g. user backed out, error overlay).
|
|
947
1412
|
if (isTerminalForWatchdog(this.computeDisplayStateNow()))
|
|
948
1413
|
return;
|
|
949
|
-
|
|
1414
|
+
let fallbackCause = "legacy query timeout";
|
|
1415
|
+
if (explicitIdle)
|
|
1416
|
+
fallbackCause = "idle ota_status";
|
|
1417
|
+
else if (rejectedStart)
|
|
1418
|
+
fallbackCause = "rejected ota_start with no recovery reply";
|
|
1419
|
+
console.log(`[OTA_PROGRESS] watchdog: ${fallbackCause} (reason=${reason}), falling back to ota_start`);
|
|
950
1420
|
this.retryCount = 0;
|
|
951
|
-
this.
|
|
952
|
-
|
|
953
|
-
this.hasReceivedAck = false;
|
|
1421
|
+
if (this.otaStartOwnership?.outcome !== "pending" && !this.prepareFreshOtaStartAttempt())
|
|
1422
|
+
return;
|
|
954
1423
|
void this.sendOtaStartWithWatchdogs();
|
|
955
1424
|
}, QUERY_REPLY_TIMEOUT_MS);
|
|
956
1425
|
}
|
|
@@ -980,12 +1449,6 @@ class OtaInstallCoordinator {
|
|
|
980
1449
|
}
|
|
981
1450
|
this.globalTimeoutStarted = false;
|
|
982
1451
|
}
|
|
983
|
-
clearPostApkDelay() {
|
|
984
|
-
if (this.postApkDelay) {
|
|
985
|
-
clearTimeout(this.postApkDelay);
|
|
986
|
-
this.postApkDelay = null;
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
1452
|
clearPingInterval() {
|
|
990
1453
|
if (this.pingInterval) {
|
|
991
1454
|
clearInterval(this.pingInterval);
|
|
@@ -1004,6 +1467,12 @@ class OtaInstallCoordinator {
|
|
|
1004
1467
|
this.continueLockoutTimer = null;
|
|
1005
1468
|
}
|
|
1006
1469
|
}
|
|
1470
|
+
clearRestartRecoveryTimeout() {
|
|
1471
|
+
if (this.restartRecoveryTimeout) {
|
|
1472
|
+
clearTimeout(this.restartRecoveryTimeout);
|
|
1473
|
+
this.restartRecoveryTimeout = null;
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1007
1476
|
clearLegacyApkSettleTimer() {
|
|
1008
1477
|
if (this.legacyApkSettleTimer) {
|
|
1009
1478
|
clearTimeout(this.legacyApkSettleTimer);
|
|
@@ -1031,7 +1500,6 @@ class OtaInstallCoordinator {
|
|
|
1031
1500
|
this.clearRetryTimeout();
|
|
1032
1501
|
this.clearStuckTimeout();
|
|
1033
1502
|
this.clearProgressTimeout();
|
|
1034
|
-
this.clearPostApkDelay();
|
|
1035
1503
|
this.clearQueryReplyTimeout();
|
|
1036
1504
|
}
|
|
1037
1505
|
clearAllOtaTimers() {
|
|
@@ -1040,6 +1508,7 @@ class OtaInstallCoordinator {
|
|
|
1040
1508
|
this.clearPingInterval();
|
|
1041
1509
|
this.clearLegacyApkSettleTimer();
|
|
1042
1510
|
this.clearMtkSimulationTimers();
|
|
1511
|
+
this.clearRestartRecoveryTimeout();
|
|
1043
1512
|
}
|
|
1044
1513
|
}
|
|
1045
1514
|
/** Singleton — one install session machine per app process (matches the single progress screen). */
|