@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,27 +11,27 @@
|
|
|
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
|
|
24
|
-
import type {OtaProgress, OtaStatus} from "@mentra/bluetooth-sdk/internal"
|
|
20
|
+
import BluetoothSdk, {type OtaProgress, type OtaStatus} from "@mentra/bluetooth-sdk"
|
|
25
21
|
import GlobalEventEmitter from "../utils/GlobalEventEmitter"
|
|
26
22
|
import {isGlassesConnected, useGlassesStore} from "../stores/glasses"
|
|
27
|
-
import {
|
|
23
|
+
import {resolveOtaManifestUrl} from "./otaManifestUrl"
|
|
24
|
+
import {hotspotOtaTransport, type HotspotOtaPhase} from "./HotspotOtaTransport"
|
|
25
|
+
import type {OtaCheckCurrentGlassesResult} from "./OtaUpdateCheckService"
|
|
28
26
|
import {deriveDisplayState, type DisplayState} from "./otaDisplayState"
|
|
29
27
|
import {
|
|
30
28
|
BES_CONTINUE_LOCKOUT_MS,
|
|
29
|
+
BES_RESTART_TIMEOUT_MS,
|
|
31
30
|
DOWNLOAD_STUCK_TIMEOUT_MS,
|
|
32
31
|
GLOBAL_OTA_TIMEOUT_MS,
|
|
33
32
|
LEGACY_APK_COMPLETION_SETTLE_MS,
|
|
34
33
|
LEGACY_BES_CONTINUE_LOCKOUT_MS,
|
|
34
|
+
LEGACY_BES_RESTART_TIMEOUT_MS,
|
|
35
35
|
LEGACY_DOWNLOAD_STUCK_TIMEOUT_MS,
|
|
36
36
|
LEGACY_GLOBAL_OTA_TIMEOUT_MS,
|
|
37
37
|
LEGACY_MTK_INSTALL_TIMEOUT_MS,
|
|
@@ -48,18 +48,41 @@ import {
|
|
|
48
48
|
MTK_INSTALL_TIMEOUT_MS,
|
|
49
49
|
OtaProgressMessages,
|
|
50
50
|
PING_INTERVAL_MS,
|
|
51
|
-
POST_APK_OTA_START_DELAY_MS,
|
|
52
51
|
PROGRESS_TIMEOUT_MS,
|
|
53
52
|
QUERY_REPLY_TIMEOUT_MS,
|
|
54
53
|
RETRY_INTERVAL_MS,
|
|
55
54
|
isLegacyShapedOtaSession,
|
|
56
55
|
isLegacyShapedOtaStatus,
|
|
56
|
+
selectOtaProtocolProfile,
|
|
57
|
+
type OtaProtocolProfile,
|
|
57
58
|
} from "./otaInstallPolicy"
|
|
58
59
|
|
|
59
60
|
function isTerminalForWatchdog(d: DisplayState): boolean {
|
|
60
61
|
return d === "complete" || d === "failed" || d === "restarting"
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
function hotspotPreflightErrorMessage(error: unknown): string {
|
|
65
|
+
const code =
|
|
66
|
+
typeof error === "object" && error !== null && "code" in error ? String((error as {code: unknown}).code) : ""
|
|
67
|
+
switch (code) {
|
|
68
|
+
case "artifact_download_failed":
|
|
69
|
+
return OtaProgressMessages.hotspotArtifactDownloadFailed
|
|
70
|
+
case "artifact_verify_failed":
|
|
71
|
+
case "manifest_invalid":
|
|
72
|
+
return OtaProgressMessages.hotspotArtifactVerifyFailed
|
|
73
|
+
case "hotspot_wifi_permission_denied":
|
|
74
|
+
return OtaProgressMessages.hotspotWifiPermissionDenied
|
|
75
|
+
case "hotspot_start_failed":
|
|
76
|
+
return OtaProgressMessages.hotspotStartFailed
|
|
77
|
+
case "hotspot_join_failed":
|
|
78
|
+
return OtaProgressMessages.hotspotJoinFailed
|
|
79
|
+
case "hotspot_server_failed":
|
|
80
|
+
return OtaProgressMessages.hotspotServerFailed
|
|
81
|
+
default:
|
|
82
|
+
return OtaProgressMessages.hotspotServerFailed
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
63
86
|
/** Non-empty when glasses are actively reporting work — drives stall timeout reset. */
|
|
64
87
|
function buildProgressStalenessSignature(
|
|
65
88
|
otaStatus: OtaStatus | null,
|
|
@@ -134,10 +157,38 @@ export interface OtaInstallSnapshot {
|
|
|
134
157
|
* +1% ticks up to 60% purely to reassure the user. Render max(real, simulated).
|
|
135
158
|
*/
|
|
136
159
|
mtkInstallStallSimulatedPercent: number | null
|
|
160
|
+
/** True for a downgrade (version-change) session — the UI narrates it differently. */
|
|
161
|
+
isVersionChange: boolean
|
|
162
|
+
/** True once the glasses reported the exact pinned target (the downgrade truly completed). */
|
|
163
|
+
versionChangeConverged: boolean
|
|
164
|
+
/**
|
|
165
|
+
* Sub-phase of the downgrade detour for the progress narrative, or null when not
|
|
166
|
+
* in one: "installing" before the handoff, "restarting" while the glasses are dark
|
|
167
|
+
* (uninstall / factory flicker / target install), "verifying" once reconnected and
|
|
168
|
+
* checking the version. Completion is the "complete" displayState.
|
|
169
|
+
*/
|
|
170
|
+
versionChangePhase: "installing" | "restarting" | "verifying" | null
|
|
171
|
+
/** Pre-ota_start phone staging/join state for a hotspot attempt. */
|
|
172
|
+
hotspotPhase: HotspotOtaPhase
|
|
173
|
+
hotspotArtifactPercent: number | null
|
|
174
|
+
/** Transport selected from the checked glasses capabilities and Wi-Fi state. */
|
|
175
|
+
transport: "wifi" | "hotspot"
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
type OtaStartOutcome = "pending" | "acknowledged" | "rejected"
|
|
179
|
+
|
|
180
|
+
interface OtaStartOwnership {
|
|
181
|
+
outcome: OtaStartOutcome
|
|
182
|
+
promise: Promise<void>
|
|
137
183
|
}
|
|
138
184
|
|
|
139
185
|
class OtaInstallCoordinator {
|
|
140
186
|
private attached = false
|
|
187
|
+
private preparedCheckResult: OtaCheckCurrentGlassesResult | null = null
|
|
188
|
+
private selectedTransport: "wifi" | "hotspot" = "wifi"
|
|
189
|
+
private hotspotManifestUrl: string | null = null
|
|
190
|
+
private hotspotPhase: HotspotOtaPhase = "idle"
|
|
191
|
+
private hotspotArtifactPercent: number | null = null
|
|
141
192
|
|
|
142
193
|
// Genuinely session-local state (was component state/refs).
|
|
143
194
|
private errorMsg = ""
|
|
@@ -157,6 +208,30 @@ class OtaInstallCoordinator {
|
|
|
157
208
|
private apkExpectedInSession = false
|
|
158
209
|
// Latched when the legacy build-number completion fires; projected as "complete".
|
|
159
210
|
private apkCompletedViaBuildIncrease = false
|
|
211
|
+
// The split routes selected legacy/unified behavior before navigation. Keep
|
|
212
|
+
// that choice sticky while an APK step upgrades ASG mid-flow.
|
|
213
|
+
private protocolProfile: OtaProtocolProfile | null = null
|
|
214
|
+
|
|
215
|
+
// BES reboot recovery. Native bridges normalize explicit BES success to a
|
|
216
|
+
// terminal status; pre-37 flows additionally retain the legacy screen's
|
|
217
|
+
// lost-FINISHED fallback. Completion still requires a BES-specific observed
|
|
218
|
+
// disconnect -> reconnect lifecycle, never the generic session reconnect flag.
|
|
219
|
+
private besRestartExpected = false
|
|
220
|
+
private besRestartDisconnectObserved = false
|
|
221
|
+
private besRestartRecovery: "awaiting" | "complete" | null = null
|
|
222
|
+
|
|
223
|
+
// Downgrade detour (version-change mode). Captured at attach from the selected
|
|
224
|
+
// update: the pinned target is a LOWER build, executed by the recovery worker via
|
|
225
|
+
// uninstall->reinstall, so the phone must NOT drive ota_start on the intermediate
|
|
226
|
+
// reconnect (the passive factory build) and completion is exact-version
|
|
227
|
+
// convergence, not a build-number increase.
|
|
228
|
+
private versionChangeSession = false
|
|
229
|
+
private versionChangeTarget: number | null = null
|
|
230
|
+
// Set once ASG reports the apk install has started (the handoff point after which
|
|
231
|
+
// ASG dies and the recovery worker owns the transaction).
|
|
232
|
+
private versionChangeInstallStarted = false
|
|
233
|
+
// Latched when a reconnect reports buildNumber === versionChangeTarget.
|
|
234
|
+
private versionChangeConverged = false
|
|
160
235
|
// Holds a legacy apk install FINISHED out of "complete" for the settle window.
|
|
161
236
|
private legacyApkSettleHold = false
|
|
162
237
|
// Display-only legacy MTK install stall simulation.
|
|
@@ -169,16 +244,22 @@ class OtaInstallCoordinator {
|
|
|
169
244
|
private retryTimeout: ReturnType<typeof setTimeout> | null = null
|
|
170
245
|
private stuckTimeout: ReturnType<typeof setTimeout> | null = null
|
|
171
246
|
private progressTimeout: ReturnType<typeof setTimeout> | null = null
|
|
172
|
-
private postApkDelay: ReturnType<typeof setTimeout> | null = null
|
|
173
247
|
private pingInterval: ReturnType<typeof setInterval> | null = null
|
|
174
248
|
private queryReplyTimeout: ReturnType<typeof setTimeout> | null = null
|
|
175
249
|
private continueLockoutTimer: ReturnType<typeof setTimeout> | null = null
|
|
250
|
+
private restartRecoveryTimeout: ReturnType<typeof setTimeout> | null = null
|
|
176
251
|
private legacyApkSettleTimer: ReturnType<typeof setTimeout> | null = null
|
|
177
252
|
private mtkStallDetectTimer: ReturnType<typeof setTimeout> | null = null
|
|
178
253
|
private mtkSimTickTimer: ReturnType<typeof setInterval> | null = null
|
|
179
254
|
|
|
180
|
-
//
|
|
181
|
-
|
|
255
|
+
// Request / retry bookkeeping (no glasses source)
|
|
256
|
+
// At most one apk-install status poll in flight (native rejects concurrent queries).
|
|
257
|
+
private apkInstallPollInFlight = false
|
|
258
|
+
// The native SDK owns one ota_start request until its application-level ack or
|
|
259
|
+
// timeout. Keep pending ownership across attach/detach, and retain the settled
|
|
260
|
+
// outcome while attached so Retry/query fallback cannot reinterpret an accepted
|
|
261
|
+
// command as permission to start a second OTA transaction.
|
|
262
|
+
private otaStartOwnership: OtaStartOwnership | null = null
|
|
182
263
|
private hasFirstActivity = false
|
|
183
264
|
// Stuck-at-zero watchdog clears only on first NON-ZERO progress; "first activity"
|
|
184
265
|
// alone (e.g. ota_status with stepPercent=0) used to clear it too eagerly.
|
|
@@ -206,6 +287,25 @@ class OtaInstallCoordinator {
|
|
|
206
287
|
|
|
207
288
|
// --- public surface (engine.ota.installSession) ---
|
|
208
289
|
|
|
290
|
+
/** Select the transport at the existing install entry point before the progress route attaches. */
|
|
291
|
+
prepare(checkResult: OtaCheckCurrentGlassesResult): "wifi" | "hotspot" {
|
|
292
|
+
const state = useGlassesStore.getState()
|
|
293
|
+
if (!state.wifiStatusKnown) {
|
|
294
|
+
throw new Error("Glasses Wi-Fi status is not available")
|
|
295
|
+
}
|
|
296
|
+
const hotspotSupported = state.hotspotOtaVersion === 1
|
|
297
|
+
const wifiConnected = state.wifi.state === "connected"
|
|
298
|
+
if (!wifiConnected && !hotspotSupported) {
|
|
299
|
+
throw new Error("Connected glasses require Wi-Fi for OTA")
|
|
300
|
+
}
|
|
301
|
+
this.preparedCheckResult = checkResult
|
|
302
|
+
this.selectedTransport = wifiConnected ? "wifi" : "hotspot"
|
|
303
|
+
this.hotspotManifestUrl = null
|
|
304
|
+
this.hotspotPhase = "idle"
|
|
305
|
+
this.hotspotArtifactPercent = null
|
|
306
|
+
return this.selectedTransport
|
|
307
|
+
}
|
|
308
|
+
|
|
209
309
|
/**
|
|
210
310
|
* Bind the state machine to a mounted progress screen. Idempotent per
|
|
211
311
|
* attach/detach cycle. Runs the initial-mount arbitration (send ota_start
|
|
@@ -216,6 +316,18 @@ class OtaInstallCoordinator {
|
|
|
216
316
|
if (this.attached) return
|
|
217
317
|
this.attached = true
|
|
218
318
|
this.resetSessionState()
|
|
319
|
+
const initialState = useGlassesStore.getState()
|
|
320
|
+
this.protocolProfile = selectOtaProtocolProfile(
|
|
321
|
+
initialState.otaStatus,
|
|
322
|
+
initialState.otaProgress,
|
|
323
|
+
initialState.buildNumber,
|
|
324
|
+
)
|
|
325
|
+
const selected = initialState.otaUpdateAvailable
|
|
326
|
+
if (selected?.isDowngrade && typeof selected.versionCode === "number" && selected.versionCode > 0) {
|
|
327
|
+
this.versionChangeSession = true
|
|
328
|
+
this.versionChangeTarget = selected.versionCode
|
|
329
|
+
console.log(`[OTA_PROGRESS] version-change (downgrade) session, target=${selected.versionCode}`)
|
|
330
|
+
}
|
|
219
331
|
this.prevConnected = isGlassesConnected(useGlassesStore.getState().connection)
|
|
220
332
|
this.storeUnsubscribe = useGlassesStore.subscribe(() => this.react())
|
|
221
333
|
// Mount pass: every reaction block runs once (like all effects on mount),
|
|
@@ -246,14 +358,67 @@ class OtaInstallCoordinator {
|
|
|
246
358
|
}
|
|
247
359
|
GlobalEventEmitter.off("ota_start_ack", this.handleAck)
|
|
248
360
|
GlobalEventEmitter.off("mtk_update_complete", this.handleMtkComplete)
|
|
361
|
+
GlobalEventEmitter.off("glasses_session_changed", this.handleGlassesSessionChanged)
|
|
249
362
|
this.clearAllOtaTimers()
|
|
250
363
|
this.clearContinueLockoutTimer()
|
|
364
|
+
// Only native command ownership survives a remount. Once the command has
|
|
365
|
+
// settled, the glasses store/durable ota_status is the session authority.
|
|
366
|
+
if (this.otaStartOwnership?.outcome !== "pending") {
|
|
367
|
+
this.otaStartOwnership = null
|
|
368
|
+
}
|
|
251
369
|
this.resetSessionState()
|
|
252
370
|
}
|
|
253
371
|
|
|
254
372
|
/** Retry after a failure: clear state and re-send ota_start (if connected). */
|
|
255
373
|
retry(): void {
|
|
256
|
-
|
|
374
|
+
// While the recovery worker owns the detour (apk install latched, not yet
|
|
375
|
+
// converged), the phone must not drive: an ota_start now would push the
|
|
376
|
+
// factory/surviving build into a parallel download-install, and a second
|
|
377
|
+
// handoff would begin()/REPLACE the live transaction under the running
|
|
378
|
+
// worker. Retry just re-enters the reconcile wait — convergence or the
|
|
379
|
+
// session timeout decides the real outcome.
|
|
380
|
+
if (this.isInVersionChangeDetour()) {
|
|
381
|
+
console.log("[OTA_PROGRESS] retry during version-change detour — re-entering wait (no ota_start)")
|
|
382
|
+
this.setErrorMsg("")
|
|
383
|
+
this.emitInternalChange()
|
|
384
|
+
return
|
|
385
|
+
}
|
|
386
|
+
const store = useGlassesStore.getState()
|
|
387
|
+
const otaStatus = store.otaStatus
|
|
388
|
+
const otaProgress = store.otaProgress
|
|
389
|
+
const authoritativeFailure = otaStatus?.status === "failed" || otaProgress?.status === "FAILED"
|
|
390
|
+
const hasUnfinishedSession =
|
|
391
|
+
(!!otaStatus &&
|
|
392
|
+
otaStatus.status !== "idle" &&
|
|
393
|
+
otaStatus.status !== "failed" &&
|
|
394
|
+
otaStatus.status !== "complete") ||
|
|
395
|
+
(!!otaProgress && otaProgress.status !== "FAILED" && otaProgress.status !== "FINISHED")
|
|
396
|
+
const shouldReconcile =
|
|
397
|
+
this.otaStartOwnership?.outcome === "pending" ||
|
|
398
|
+
(!authoritativeFailure &&
|
|
399
|
+
(this.otaStartOwnership?.outcome === "acknowledged" || this.hasFirstActivity || hasUnfinishedSession))
|
|
400
|
+
|
|
401
|
+
if (shouldReconcile) {
|
|
402
|
+
console.log("[OTA_PROGRESS] retry pressed for an existing OTA session — reconciling without ota_start")
|
|
403
|
+
this.clearPerStepTimers()
|
|
404
|
+
this.clearLegacyApkSettleTimer()
|
|
405
|
+
this.clearMtkSimulationTimers()
|
|
406
|
+
this.clearContinueLockoutTimer()
|
|
407
|
+
this.setErrorMsg("")
|
|
408
|
+
this.maybeStartGlobalTimeout()
|
|
409
|
+
this.maybeArmStuckWatchdog()
|
|
410
|
+
if (isGlassesConnected(store.connection)) {
|
|
411
|
+
if (this.otaStartOwnership?.outcome === "pending") {
|
|
412
|
+
void this.sendOtaStartWithWatchdogs()
|
|
413
|
+
} else {
|
|
414
|
+
void BluetoothSdk.queryOtaStatus().catch(() => {})
|
|
415
|
+
this.armQueryReplyFallback("retry")
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
console.log("[OTA_PROGRESS] retry pressed after a rejected or terminal attempt — starting a fresh ota_start")
|
|
257
422
|
// Batch like the old event handler: React ran the whole handler (including
|
|
258
423
|
// the ota_start send) before re-rendering + re-running effects.
|
|
259
424
|
const wasReacting = this.reacting
|
|
@@ -262,16 +427,16 @@ class OtaInstallCoordinator {
|
|
|
262
427
|
this.clearPerStepTimers()
|
|
263
428
|
this.clearLegacyApkSettleTimer()
|
|
264
429
|
this.clearMtkSimulationTimers()
|
|
430
|
+
this.clearContinueLockoutTimer()
|
|
265
431
|
this.retryCount = 0
|
|
266
|
-
this.
|
|
267
|
-
this.
|
|
432
|
+
if (!this.prepareFreshOtaStartAttempt()) return
|
|
433
|
+
this.resetBesRestartAttempt()
|
|
268
434
|
this.setSawReconnectEdge(false)
|
|
269
435
|
this.setErrorMsg("")
|
|
270
|
-
this.
|
|
436
|
+
this.setContinueButtonDisabled(false)
|
|
271
437
|
this.setLegacyApkSettleHold(false)
|
|
272
438
|
this.setMtkSimulatedPercent(null)
|
|
273
439
|
this.lastRealMtkProgress = 0
|
|
274
|
-
const store = useGlassesStore.getState()
|
|
275
440
|
store.setOtaProgress(null)
|
|
276
441
|
store.setOtaStatus(null)
|
|
277
442
|
if (isGlassesConnected(useGlassesStore.getState().connection)) {
|
|
@@ -288,12 +453,17 @@ class OtaInstallCoordinator {
|
|
|
288
453
|
* stays in the screen): clear the selected update, and after an APK step
|
|
289
454
|
* clear the stale build number so the next check re-reads version_info.
|
|
290
455
|
*/
|
|
291
|
-
finish(): void {
|
|
456
|
+
async finish(): Promise<void> {
|
|
457
|
+
if (this.otaStartOwnership?.outcome !== "pending") {
|
|
458
|
+
this.otaStartOwnership = null
|
|
459
|
+
}
|
|
292
460
|
useGlassesStore.getState().setOtaUpdateAvailable(null)
|
|
293
461
|
if (this.apkStepSeen) {
|
|
294
|
-
BluetoothSdk.updateGlasses({buildNumber: ""})
|
|
295
462
|
useGlassesStore.getState().setGlassesInfo({buildNumber: ""})
|
|
296
463
|
}
|
|
464
|
+
// The post-install check needs internet again. Do not let callers leave the
|
|
465
|
+
// terminal screen while Android still owns the no-internet hotspot route.
|
|
466
|
+
await this.teardownHotspotTransport()
|
|
297
467
|
}
|
|
298
468
|
|
|
299
469
|
/**
|
|
@@ -302,8 +472,8 @@ class OtaInstallCoordinator {
|
|
|
302
472
|
* deriveDisplayState doesn't resurrect the stale install when the host
|
|
303
473
|
* navigates away and back through /ota routes.
|
|
304
474
|
*/
|
|
305
|
-
discard(): void {
|
|
306
|
-
this.finish()
|
|
475
|
+
async discard(): Promise<void> {
|
|
476
|
+
await this.finish()
|
|
307
477
|
const store = useGlassesStore.getState()
|
|
308
478
|
store.setOtaStatus(null)
|
|
309
479
|
store.setOtaProgress(null)
|
|
@@ -323,17 +493,45 @@ class OtaInstallCoordinator {
|
|
|
323
493
|
sawReconnectEdge: this.sawReconnectEdge,
|
|
324
494
|
legacyApkSettleHold: this.legacyApkSettleHold,
|
|
325
495
|
apkCompletedViaBuildIncrease: this.apkCompletedViaBuildIncrease,
|
|
496
|
+
besRestartRecovery: this.besRestartRecovery,
|
|
497
|
+
versionChangeConverged: this.versionChangeConverged,
|
|
498
|
+
versionChangeSession: this.versionChangeSession,
|
|
326
499
|
}),
|
|
327
500
|
errorMsg: this.errorMsg,
|
|
328
|
-
|
|
501
|
+
// Keep Continue disabled while the expected BES reboot is still in flight.
|
|
502
|
+
continueButtonDisabled: this.continueButtonDisabled || this.besRestartRecovery === "awaiting",
|
|
329
503
|
connected,
|
|
330
504
|
// Copies: the snapshot must not hand callers mutable references into the store.
|
|
331
505
|
otaStatus: otaStatus ? {...otaStatus} : null,
|
|
332
506
|
otaProgress: otaProgress ? {...otaProgress} : null,
|
|
333
507
|
mtkInstallStallSimulatedPercent: this.mtkSimulatedPercent,
|
|
508
|
+
isVersionChange: this.versionChangeSession,
|
|
509
|
+
versionChangeConverged: this.versionChangeConverged,
|
|
510
|
+
versionChangePhase: this.deriveVersionChangePhase(connected),
|
|
511
|
+
hotspotPhase: this.hotspotPhase,
|
|
512
|
+
hotspotArtifactPercent: this.hotspotArtifactPercent,
|
|
513
|
+
transport: this.selectedTransport,
|
|
334
514
|
}
|
|
335
515
|
}
|
|
336
516
|
|
|
517
|
+
/**
|
|
518
|
+
* True while the recovery worker owns the downgrade transaction (after the apk install
|
|
519
|
+
* started, before convergence). During this window ASG is being uninstalled/reinstalled and
|
|
520
|
+
* cannot answer the phone, so the coordinator must not ping, poll ota_query_status, or arm the
|
|
521
|
+
* progress-stall watchdog — doing so drives the passive factory build and fails the session
|
|
522
|
+
* before the target reconnects. Completion is exact-version convergence, detected separately.
|
|
523
|
+
*/
|
|
524
|
+
private isInVersionChangeDetour(): boolean {
|
|
525
|
+
return this.versionChangeSession && this.versionChangeInstallStarted && !this.versionChangeConverged
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/** Downgrade-detour sub-phase for the progress narrative (see OtaInstallSnapshot). */
|
|
529
|
+
private deriveVersionChangePhase(connected: boolean): "installing" | "restarting" | "verifying" | null {
|
|
530
|
+
if (!this.versionChangeSession || this.versionChangeConverged) return null
|
|
531
|
+
if (!this.versionChangeInstallStarted) return "installing"
|
|
532
|
+
return connected ? "verifying" : "restarting"
|
|
533
|
+
}
|
|
534
|
+
|
|
337
535
|
/**
|
|
338
536
|
* Subscribe to install snapshot changes, deduped on the projected JSON.
|
|
339
537
|
* Fires on glasses-store changes AND coordinator-internal state changes
|
|
@@ -367,10 +565,16 @@ class OtaInstallCoordinator {
|
|
|
367
565
|
this.initialBuildNumber = null
|
|
368
566
|
this.apkExpectedInSession = false
|
|
369
567
|
this.apkCompletedViaBuildIncrease = false
|
|
568
|
+
this.protocolProfile = null
|
|
569
|
+
this.resetBesRestartAttempt()
|
|
570
|
+
this.versionChangeSession = false
|
|
571
|
+
this.versionChangeTarget = null
|
|
572
|
+
this.versionChangeInstallStarted = false
|
|
573
|
+
this.versionChangeConverged = false
|
|
370
574
|
this.legacyApkSettleHold = false
|
|
371
575
|
this.mtkSimulatedPercent = null
|
|
372
576
|
this.lastRealMtkProgress = 0
|
|
373
|
-
this.
|
|
577
|
+
this.apkInstallPollInFlight = false
|
|
374
578
|
this.hasFirstActivity = false
|
|
375
579
|
this.hasFirstNonZeroProgress = false
|
|
376
580
|
this.retryCount = 0
|
|
@@ -385,6 +589,26 @@ class OtaInstallCoordinator {
|
|
|
385
589
|
this.reactQueued = false
|
|
386
590
|
}
|
|
387
591
|
|
|
592
|
+
/** Begin a genuinely new command attempt only after native ownership has settled. */
|
|
593
|
+
private prepareFreshOtaStartAttempt(): boolean {
|
|
594
|
+
if (this.otaStartOwnership?.outcome === "pending") {
|
|
595
|
+
console.error("[OTA_PROGRESS] refused fresh ota_start while native still owns the previous request")
|
|
596
|
+
return false
|
|
597
|
+
}
|
|
598
|
+
this.otaStartOwnership = null
|
|
599
|
+
this.hasFirstActivity = false
|
|
600
|
+
this.hasFirstNonZeroProgress = false
|
|
601
|
+
this.setApkCompletedViaBuildIncrease(false)
|
|
602
|
+
return true
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/** Reset only the reboot lifecycle that belongs to one install attempt. */
|
|
606
|
+
private resetBesRestartAttempt(): void {
|
|
607
|
+
this.besRestartExpected = false
|
|
608
|
+
this.besRestartDisconnectObserved = false
|
|
609
|
+
this.besRestartRecovery = null
|
|
610
|
+
}
|
|
611
|
+
|
|
388
612
|
/** Notify snapshot subscribers of a coordinator-internal state change. */
|
|
389
613
|
private emitInternalChange(): void {
|
|
390
614
|
for (const check of Array.from(this.snapshotCheckers)) check()
|
|
@@ -434,18 +658,21 @@ class OtaInstallCoordinator {
|
|
|
434
658
|
this.emitInternalChange()
|
|
435
659
|
}
|
|
436
660
|
|
|
437
|
-
/**
|
|
438
|
-
* Legacy-session shape (WP 8C): event shape wins once events exist; before any event
|
|
439
|
-
* only the session-start build number can tell (falls back to the live build number
|
|
440
|
-
* until it is captured). Old builds get the LEGACY_* padded policy durations.
|
|
441
|
-
*/
|
|
661
|
+
/** Legacy/unified policy is selected once, matching the old sticky route choice. */
|
|
442
662
|
private isLegacySessionShapeNow(): boolean {
|
|
663
|
+
if (this.protocolProfile) return this.protocolProfile === "legacy"
|
|
443
664
|
const state = useGlassesStore.getState()
|
|
444
|
-
|
|
665
|
+
const selected = selectOtaProtocolProfile(
|
|
445
666
|
state.otaStatus,
|
|
446
667
|
state.otaProgress,
|
|
447
668
|
this.initialBuildNumber || state.buildNumber,
|
|
448
669
|
)
|
|
670
|
+
if (selected) {
|
|
671
|
+
this.protocolProfile = selected
|
|
672
|
+
console.log(`[OTA_PROGRESS] protocol profile selected: ${selected}`)
|
|
673
|
+
return selected === "legacy"
|
|
674
|
+
}
|
|
675
|
+
return isLegacyShapedOtaSession(state.otaStatus, state.otaProgress, state.buildNumber)
|
|
449
676
|
}
|
|
450
677
|
|
|
451
678
|
private react(): void {
|
|
@@ -498,6 +725,19 @@ class OtaInstallCoordinator {
|
|
|
498
725
|
|
|
499
726
|
const legacySession = this.isLegacySessionShapeNow()
|
|
500
727
|
|
|
728
|
+
if (this.isBesRestartExpected(otaStatus, otaProgress, legacySession)) {
|
|
729
|
+
this.besRestartExpected = true
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// Port the legacy route's actual reboot invariant before deriving display
|
|
733
|
+
// state, so reconnecting with stale BES in_progress@100 never renders updating.
|
|
734
|
+
if (connectedChanged && !connected && this.besRestartExpected && this.besRestartRecovery === null) {
|
|
735
|
+
this.besRestartDisconnectObserved = true
|
|
736
|
+
this.besRestartRecovery = "awaiting"
|
|
737
|
+
console.log("[OTA_PROGRESS] BES restart disconnect observed — awaiting reconnect verification")
|
|
738
|
+
}
|
|
739
|
+
if (this.besRestartRecovery === "awaiting" && connected) this.completeBesRestartRecovery()
|
|
740
|
+
|
|
501
741
|
// Derived UI state — the glasses data IS the source of truth.
|
|
502
742
|
const displayState = deriveDisplayState({
|
|
503
743
|
otaStatus,
|
|
@@ -507,6 +747,9 @@ class OtaInstallCoordinator {
|
|
|
507
747
|
sawReconnectEdge: this.sawReconnectEdge,
|
|
508
748
|
legacyApkSettleHold: this.legacyApkSettleHold,
|
|
509
749
|
apkCompletedViaBuildIncrease: this.apkCompletedViaBuildIncrease,
|
|
750
|
+
besRestartRecovery: this.besRestartRecovery,
|
|
751
|
+
versionChangeConverged: this.versionChangeConverged,
|
|
752
|
+
versionChangeSession: this.versionChangeSession,
|
|
510
753
|
})
|
|
511
754
|
const stallSig = buildProgressStalenessSignature(otaStatus, otaProgress, displayState)
|
|
512
755
|
const stallDuration = progressTimeoutDurationMs(otaStatus, otaProgress, legacySession)
|
|
@@ -572,6 +815,73 @@ class OtaInstallCoordinator {
|
|
|
572
815
|
useGlassesStore.getState().setMtkUpdatedThisSession(true)
|
|
573
816
|
}
|
|
574
817
|
|
|
818
|
+
// Version-change (downgrade) install started: once ASG reports the apk install
|
|
819
|
+
// phase, the handoff to the recovery worker has happened. From here the phone
|
|
820
|
+
// stops driving ota_start (see runConnectEdge) and waits for exact-version
|
|
821
|
+
// convergence — the wipe means no resumable ASG session and no build increase.
|
|
822
|
+
if (
|
|
823
|
+
this.versionChangeSession &&
|
|
824
|
+
!this.versionChangeInstallStarted &&
|
|
825
|
+
// Only latch from a status that belongs to THIS session: an install/apk status is a
|
|
826
|
+
// response to our own acknowledged ota_start, so a leftover from a prior session
|
|
827
|
+
// sitting in the store at mount cannot prematurely enter the detour wait (which would
|
|
828
|
+
// suppress ota_start and hang the fresh downgrade until the global timeout).
|
|
829
|
+
this.otaStartOwnership?.outcome === "acknowledged" &&
|
|
830
|
+
otaStatus?.stepType === "apk" &&
|
|
831
|
+
otaStatus.phase === "install"
|
|
832
|
+
) {
|
|
833
|
+
console.log("[OTA_PROGRESS] version-change: apk install started — entering detour wait")
|
|
834
|
+
this.versionChangeInstallStarted = true
|
|
835
|
+
this.emitInternalChange()
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// Ownership refuted: ASG emits install/STARTED BEFORE handing off, so the latch above can
|
|
839
|
+
// be set even when no transaction ends up owning the detour. Recovery answers every
|
|
840
|
+
// handoff synchronously, so exactly three error codes prove non-ownership and release the
|
|
841
|
+
// latch: downgrade_handoff_refused (recovery's explicit verdict),
|
|
842
|
+
// downgrade_handoff_failed (no verdict at all — recovery dead/missing, so it never began),
|
|
843
|
+
// and downgrade_transaction_stalled (the long-stop past recovery's own stale give-up).
|
|
844
|
+
// An accepted-but-slow transaction emits NONE of these (acceptance cancels the short
|
|
845
|
+
// watchdog), so the latch is never released while a live worker owns the staged artifact
|
|
846
|
+
// — which recovery additionally claims by rename at acceptance.
|
|
847
|
+
if (
|
|
848
|
+
this.versionChangeInstallStarted &&
|
|
849
|
+
!this.versionChangeConverged &&
|
|
850
|
+
otaStatus?.status === "failed" &&
|
|
851
|
+
(otaStatus.error === "downgrade_handoff_refused" ||
|
|
852
|
+
otaStatus.error === "downgrade_handoff_failed" ||
|
|
853
|
+
otaStatus.error === "downgrade_transaction_stalled")
|
|
854
|
+
) {
|
|
855
|
+
console.log(
|
|
856
|
+
`[OTA_PROGRESS] version-change: no transaction owns the detour (${otaStatus.error}) — releasing latch`,
|
|
857
|
+
)
|
|
858
|
+
this.versionChangeInstallStarted = false
|
|
859
|
+
this.emitInternalChange()
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// Version-change convergence: the reconnected glasses report exactly the pinned
|
|
863
|
+
// target. This is the ONLY completion signal for a downgrade (lower build, no
|
|
864
|
+
// increase, wiped session). Mirrors the legacy build-increase latch but by equality.
|
|
865
|
+
if (
|
|
866
|
+
this.versionChangeSession &&
|
|
867
|
+
this.versionChangeInstallStarted &&
|
|
868
|
+
!this.versionChangeConverged &&
|
|
869
|
+
this.versionChangeTarget !== null &&
|
|
870
|
+
buildNumberChanged &&
|
|
871
|
+
buildNumber
|
|
872
|
+
) {
|
|
873
|
+
const currentBuild = Number.parseInt(buildNumber, 10)
|
|
874
|
+
if (Number.isFinite(currentBuild) && currentBuild === this.versionChangeTarget) {
|
|
875
|
+
console.log(`[OTA_PROGRESS] version-change converged: glasses report target ${currentBuild}, completing`)
|
|
876
|
+
this.clearPerStepTimers()
|
|
877
|
+
this.clearGlobalTimeout()
|
|
878
|
+
this.apkStepSeen = true
|
|
879
|
+
this.setErrorMsg("")
|
|
880
|
+
this.versionChangeConverged = true
|
|
881
|
+
this.emitInternalChange()
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
575
885
|
// Legacy APK completion by build-number increase (WP 8C-c): old builds reboot into
|
|
576
886
|
// the new build without a reliable explicit reconnect status; the fresh version_info
|
|
577
887
|
// build number is the completion signal (ported from progress-legacy.tsx, which
|
|
@@ -614,7 +924,7 @@ class OtaInstallCoordinator {
|
|
|
614
924
|
|
|
615
925
|
/**
|
|
616
926
|
* Connect-edge — the only place that decides to send ota_start /
|
|
617
|
-
* ota_query_status
|
|
927
|
+
* ota_query_status and flips sawReconnectEdge
|
|
618
928
|
* on the false -> true transition that signals the BES reboot completed.
|
|
619
929
|
*/
|
|
620
930
|
if (connectedChanged) {
|
|
@@ -628,23 +938,26 @@ class OtaInstallCoordinator {
|
|
|
628
938
|
if (isMount) {
|
|
629
939
|
GlobalEventEmitter.on("ota_start_ack", this.handleAck)
|
|
630
940
|
GlobalEventEmitter.on("mtk_update_complete", this.handleMtkComplete)
|
|
941
|
+
GlobalEventEmitter.on("glasses_session_changed", this.handleGlassesSessionChanged)
|
|
631
942
|
}
|
|
632
943
|
|
|
633
944
|
// Ping keepalive while an OTA is actively running (legacy sessions pinged on the
|
|
634
945
|
// padded interval — old builds slept less eagerly and pings are costlier for them).
|
|
635
946
|
if (connectedChanged || displayStateChanged) {
|
|
636
947
|
this.clearPingInterval()
|
|
637
|
-
const active =
|
|
948
|
+
const active =
|
|
949
|
+
connected && (displayState === "starting" || displayState === "updating") && !this.isInVersionChangeDetour()
|
|
638
950
|
if (active) {
|
|
639
951
|
void BluetoothSdk.ping().catch(() => {})
|
|
640
952
|
const pingIntervalMs = legacySession ? LEGACY_PING_INTERVAL_MS : PING_INTERVAL_MS
|
|
641
953
|
this.pingInterval = setInterval(() => {
|
|
642
954
|
void BluetoothSdk.ping().catch(() => {})
|
|
955
|
+
this.maybePollApkInstallStatus()
|
|
643
956
|
}, pingIntervalMs)
|
|
644
957
|
}
|
|
645
958
|
}
|
|
646
959
|
|
|
647
|
-
// Any glasses activity
|
|
960
|
+
// Any glasses activity proves the logical ota_start was accepted.
|
|
648
961
|
if ((otaStatusChanged || otaProgressChanged) && (otaStatus || otaProgress)) {
|
|
649
962
|
this.onFirstActivity()
|
|
650
963
|
}
|
|
@@ -667,7 +980,10 @@ class OtaInstallCoordinator {
|
|
|
667
980
|
// update that doesn't change the signature keeps the same timer running
|
|
668
981
|
// instead of forever re-extending the deadline.
|
|
669
982
|
if (stallSigChanged || displayStateChanged) {
|
|
670
|
-
if (
|
|
983
|
+
if (this.isInVersionChangeDetour()) {
|
|
984
|
+
// Recovery worker owns the transaction; there are no progress events to stall on.
|
|
985
|
+
this.clearProgressTimeout()
|
|
986
|
+
} else if (displayState !== "starting" && displayState !== "updating") {
|
|
671
987
|
this.clearProgressTimeout()
|
|
672
988
|
} else if (!stallSig) {
|
|
673
989
|
this.clearProgressTimeout()
|
|
@@ -685,20 +1001,24 @@ class OtaInstallCoordinator {
|
|
|
685
1001
|
|
|
686
1002
|
// Lockout on Continue button after BES restart to prevent accidental tap
|
|
687
1003
|
// (15s unified, 35s for legacy-shaped sessions — WP 8C-f).
|
|
688
|
-
if (displayStateChanged) {
|
|
1004
|
+
if (displayStateChanged && displayState === "restarting") {
|
|
689
1005
|
this.clearContinueLockoutTimer()
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
1006
|
+
this.clearRestartRecoveryTimeout()
|
|
1007
|
+
const lockoutMs = legacySession ? LEGACY_BES_CONTINUE_LOCKOUT_MS : BES_CONTINUE_LOCKOUT_MS
|
|
1008
|
+
const restartTimeoutMs = legacySession ? LEGACY_BES_RESTART_TIMEOUT_MS : BES_RESTART_TIMEOUT_MS
|
|
1009
|
+
this.setContinueButtonDisabled(true)
|
|
1010
|
+
this.continueLockoutTimer = setTimeout(() => {
|
|
1011
|
+
this.continueLockoutTimer = null
|
|
1012
|
+
this.setContinueButtonDisabled(false)
|
|
1013
|
+
}, lockoutMs)
|
|
1014
|
+
this.restartRecoveryTimeout = setTimeout(() => {
|
|
1015
|
+
this.restartRecoveryTimeout = null
|
|
1016
|
+
if (this.computeDisplayStateNow() !== "restarting") return
|
|
1017
|
+
console.log(`[OTA_PROGRESS] watchdog: glasses did not finish restarting in ${restartTimeoutMs}ms`)
|
|
1018
|
+
this.setErrorMsg(OtaProgressMessages.restartTimeout)
|
|
1019
|
+
}, restartTimeoutMs)
|
|
1020
|
+
} else if (displayStateChanged) {
|
|
1021
|
+
this.clearRestartRecoveryTimeout()
|
|
702
1022
|
}
|
|
703
1023
|
|
|
704
1024
|
// Legacy MTK install stall simulation (WP 8C-e): display-only. The simulation
|
|
@@ -716,8 +1036,54 @@ class OtaInstallCoordinator {
|
|
|
716
1036
|
displayStateChanged &&
|
|
717
1037
|
(displayState === "complete" || displayState === "restarting" || displayState === "failed")
|
|
718
1038
|
) {
|
|
1039
|
+
if (displayState === "failed") {
|
|
1040
|
+
this.clearPerStepTimers()
|
|
1041
|
+
}
|
|
719
1042
|
useGlassesStore.getState().setOtaUpdateAvailable(null)
|
|
1043
|
+
if (displayState === "complete" || displayState === "failed") {
|
|
1044
|
+
void this.teardownHotspotTransport()
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
/**
|
|
1050
|
+
* Reconnect arbitration shared by the physical BLE connect edge and the
|
|
1051
|
+
* glasses_session_changed signal. The latter exists because the BES keeps the BLE
|
|
1052
|
+
* link alive across asg_client restarts (APK OTA): no physical edge ever fires, so
|
|
1053
|
+
* the changed process session id is the only reconnect signal the coordinator gets
|
|
1054
|
+
* (incident rep_01KY6BJ0B7A4RBMQ7VN39KAE5E). Always returns true (the caller's
|
|
1055
|
+
* edge handling is complete).
|
|
1056
|
+
*/
|
|
1057
|
+
private runReconnectArbitration(label: string): boolean {
|
|
1058
|
+
console.log(`[OTA_PROGRESS] ${label}: false->true, flipping sawReconnectEdge=true`)
|
|
1059
|
+
// A physical reconnect passed through the disconnect branch first, which cleared
|
|
1060
|
+
// these timers; a session-change edge never disconnects, so a fallback armed by an
|
|
1061
|
+
// earlier mount/query could otherwise fire alongside the arbitration below and send
|
|
1062
|
+
// a duplicate ota_start. Clearing here is a no-op on the physical path.
|
|
1063
|
+
this.clearQueryReplyTimeout()
|
|
1064
|
+
this.setSawReconnectEdge(true)
|
|
1065
|
+
|
|
1066
|
+
if (this.besRestartDisconnectObserved) {
|
|
1067
|
+
console.log(`[OTA_PROGRESS] ${label}: BES reboot reconnect observed — no ota_query_status`)
|
|
1068
|
+
this.completeBesRestartRecovery()
|
|
1069
|
+
return true
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
// Legacy apk settle window (WP 8C): the glasses restarting into the new build is
|
|
1073
|
+
// the EXPECTED reconnect here — the legacy screen took no action on it. Skip the
|
|
1074
|
+
// query/fallback; the settle timer (or the build-number increase) completes.
|
|
1075
|
+
if (this.legacyApkSettleHold) {
|
|
1076
|
+
console.log(`[OTA_PROGRESS] ${label}: reconnect during legacy apk settle hold — no query`)
|
|
1077
|
+
return true
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
console.log(`[OTA_PROGRESS] ${label}: reconnected, sending ota_query_status`)
|
|
1081
|
+
if (this.otaStartOwnership?.outcome === "pending") {
|
|
1082
|
+
void this.sendOtaStartWithWatchdogs()
|
|
720
1083
|
}
|
|
1084
|
+
void BluetoothSdk.queryOtaStatus().catch(() => {})
|
|
1085
|
+
this.armQueryReplyFallback("reconnect")
|
|
1086
|
+
return true
|
|
721
1087
|
}
|
|
722
1088
|
|
|
723
1089
|
private runConnectEdge(connected: boolean): void {
|
|
@@ -726,54 +1092,30 @@ class OtaInstallCoordinator {
|
|
|
726
1092
|
|
|
727
1093
|
if (!connected) {
|
|
728
1094
|
console.log("[OTA_PROGRESS] connect-edge: disconnected")
|
|
729
|
-
|
|
730
|
-
//
|
|
731
|
-
//
|
|
732
|
-
// re-queries and re-arms this fallback anyway.
|
|
1095
|
+
// Also drop pending query/retry sends: firing ota_start into a dead link
|
|
1096
|
+
// can only produce a false "failed" state. Reconnect arbitration first
|
|
1097
|
+
// queries durable status and re-arms the one permitted fallback.
|
|
733
1098
|
this.clearQueryReplyTimeout()
|
|
1099
|
+
this.clearRetryTimeout()
|
|
734
1100
|
return
|
|
735
1101
|
}
|
|
736
1102
|
|
|
737
1103
|
const becameConnected = prev === false && connected === true
|
|
738
|
-
if (becameConnected) {
|
|
739
|
-
console.log("[OTA_PROGRESS] connect-edge: false->true, flipping sawReconnectEdge=true")
|
|
740
|
-
this.setSawReconnectEdge(true)
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
// Legacy apk settle window (WP 8C): the glasses restarting into the new build is
|
|
744
|
-
// the EXPECTED reconnect here — the legacy screen took no action on it. Skip the
|
|
745
|
-
// query/fallback; the settle timer (or the build-number increase) completes.
|
|
746
|
-
if (becameConnected && this.legacyApkSettleHold) {
|
|
747
|
-
console.log("[OTA_PROGRESS] connect-edge: reconnect during legacy apk settle hold — no query")
|
|
1104
|
+
if (becameConnected && this.runReconnectArbitration("connect-edge")) {
|
|
748
1105
|
return
|
|
749
1106
|
}
|
|
750
1107
|
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
if (
|
|
757
|
-
console.log("[OTA_PROGRESS] connect-edge:
|
|
758
|
-
this.clearPostApkDelay()
|
|
759
|
-
this.postApkDelay = setTimeout(() => {
|
|
760
|
-
this.postApkDelay = null
|
|
761
|
-
this.retryCount = 0
|
|
762
|
-
this.hasFirstActivity = false
|
|
763
|
-
this.hasFirstNonZeroProgress = false
|
|
764
|
-
this.hasReceivedAck = false
|
|
765
|
-
console.log("[OTA_PROGRESS] POST_APK delay fired, sending ota_start")
|
|
766
|
-
void this.sendOtaStartWithWatchdogs()
|
|
767
|
-
}, POST_APK_OTA_START_DELAY_MS)
|
|
1108
|
+
// Version-change detour: after the install started, EVERY reconnect is expected
|
|
1109
|
+
// (the passive factory build, then the target). Do NOT send ota_start/query — the
|
|
1110
|
+
// factory build would treat the pin as a plain upgrade and start a redundant
|
|
1111
|
+
// download racing the recovery worker. Convergence (buildNumber === target) is
|
|
1112
|
+
// detected in the reaction pass; here we simply take no action.
|
|
1113
|
+
if (this.versionChangeSession && this.versionChangeInstallStarted && !this.versionChangeConverged) {
|
|
1114
|
+
console.log("[OTA_PROGRESS] connect-edge: reconnect during version-change detour — no query (expected)")
|
|
768
1115
|
return
|
|
769
1116
|
}
|
|
770
1117
|
|
|
771
|
-
|
|
772
|
-
console.log("[OTA_PROGRESS] connect-edge: reconnected, sending ota_query_status")
|
|
773
|
-
void BluetoothSdk.sendOtaQueryStatus()
|
|
774
|
-
this.armQueryReplyFallback("reconnect")
|
|
775
|
-
return
|
|
776
|
-
}
|
|
1118
|
+
const storeState = useGlassesStore.getState()
|
|
777
1119
|
|
|
778
1120
|
// Initial mount (prev === current === true). If no session yet, kick off ota_start.
|
|
779
1121
|
// An idle ota_status has an empty sessionId; treat it as no session so ota_start
|
|
@@ -781,19 +1123,25 @@ class OtaInstallCoordinator {
|
|
|
781
1123
|
const isIdleStatus = !!storeState.otaStatus && storeState.otaStatus.sessionId === ""
|
|
782
1124
|
const noSessionYet = (!storeState.otaStatus && !storeState.otaProgress) || isIdleStatus
|
|
783
1125
|
if (noSessionYet) {
|
|
1126
|
+
if (!isIdleStatus && this.otaStartOwnership?.outcome === "acknowledged") {
|
|
1127
|
+
console.log("[OTA_PROGRESS] initial mount, acknowledged session has no cached status — reconciling")
|
|
1128
|
+
void BluetoothSdk.queryOtaStatus().catch(() => {})
|
|
1129
|
+
this.armQueryReplyFallback("initial-mount")
|
|
1130
|
+
return
|
|
1131
|
+
}
|
|
784
1132
|
console.log(
|
|
785
1133
|
isIdleStatus
|
|
786
1134
|
? "[OTA_PROGRESS] initial mount, idle status (no sessionId), sending ota_start"
|
|
787
1135
|
: "[OTA_PROGRESS] initial mount, no session in store, sending ota_start",
|
|
788
1136
|
)
|
|
789
1137
|
this.retryCount = 0
|
|
790
|
-
this.
|
|
791
|
-
|
|
792
|
-
|
|
1138
|
+
if (isIdleStatus && this.otaStartOwnership?.outcome !== "pending" && !this.prepareFreshOtaStartAttempt()) {
|
|
1139
|
+
return
|
|
1140
|
+
}
|
|
793
1141
|
void this.sendOtaStartWithWatchdogs()
|
|
794
1142
|
} else {
|
|
795
1143
|
console.log("[OTA_PROGRESS] initial mount, session exists, sending ota_query_status")
|
|
796
|
-
void BluetoothSdk.
|
|
1144
|
+
void BluetoothSdk.queryOtaStatus().catch(() => {})
|
|
797
1145
|
this.armQueryReplyFallback("initial-mount")
|
|
798
1146
|
}
|
|
799
1147
|
}
|
|
@@ -801,18 +1149,28 @@ class OtaInstallCoordinator {
|
|
|
801
1149
|
// --- BLE OTA event listeners (engine GlobalEventEmitter, fed by OtaService) ---
|
|
802
1150
|
|
|
803
1151
|
private readonly handleAck = (): void => {
|
|
804
|
-
|
|
1152
|
+
const ownership = this.otaStartOwnership
|
|
1153
|
+
if (!ownership || ownership.outcome === "rejected") return
|
|
1154
|
+
if (ownership.outcome === "acknowledged") return
|
|
805
1155
|
console.log("[OTA_PROGRESS] ota_start_ack received")
|
|
806
|
-
|
|
1156
|
+
ownership.outcome = "acknowledged"
|
|
807
1157
|
this.clearRetryTimeout()
|
|
808
1158
|
}
|
|
809
1159
|
|
|
1160
|
+
/**
|
|
1161
|
+
* The asg process restarted while the BES kept the BLE link up (sid change observed
|
|
1162
|
+
* by the bridge): treat it exactly like a physical reconnect edge.
|
|
1163
|
+
*/
|
|
1164
|
+
private readonly handleGlassesSessionChanged = (): void => {
|
|
1165
|
+
this.runReconnectArbitration("session-change")
|
|
1166
|
+
}
|
|
1167
|
+
|
|
810
1168
|
private readonly handleMtkComplete = (): void => {
|
|
811
1169
|
console.log("[OTA_PROGRESS] mtk_update_complete received")
|
|
812
1170
|
this.clearProgressTimeout()
|
|
813
1171
|
this.onFirstActivity()
|
|
814
1172
|
this.onFirstNonZeroProgress()
|
|
815
|
-
void BluetoothSdk.
|
|
1173
|
+
void BluetoothSdk.queryOtaStatus().catch(() => {})
|
|
816
1174
|
useGlassesStore.getState().setMtkUpdatedThisSession(true)
|
|
817
1175
|
}
|
|
818
1176
|
|
|
@@ -834,7 +1192,9 @@ class OtaInstallCoordinator {
|
|
|
834
1192
|
}
|
|
835
1193
|
|
|
836
1194
|
if (this.errorMsg) {
|
|
837
|
-
console.log(
|
|
1195
|
+
console.log(
|
|
1196
|
+
"[OTA_PROGRESS] legacy install complete arrived after a failure — completing (legacy last-write-wins)",
|
|
1197
|
+
)
|
|
838
1198
|
this.setErrorMsg("")
|
|
839
1199
|
this.clearLegacyApkSettleTimer()
|
|
840
1200
|
this.setLegacyApkSettleHold(false)
|
|
@@ -860,6 +1220,43 @@ class OtaInstallCoordinator {
|
|
|
860
1220
|
}
|
|
861
1221
|
}
|
|
862
1222
|
|
|
1223
|
+
private isBesRestartExpected(
|
|
1224
|
+
otaStatus: OtaStatus | null,
|
|
1225
|
+
otaProgress: OtaProgress | null,
|
|
1226
|
+
legacySession: boolean,
|
|
1227
|
+
): boolean {
|
|
1228
|
+
const statusReadyToRestart =
|
|
1229
|
+
otaStatus?.stepType === "bes" &&
|
|
1230
|
+
otaStatus.phase === "install" &&
|
|
1231
|
+
(otaStatus.status === "step_complete" || otaStatus.status === "complete")
|
|
1232
|
+
const progressReadyToRestart =
|
|
1233
|
+
legacySession &&
|
|
1234
|
+
otaProgress?.currentUpdate === "bes" &&
|
|
1235
|
+
otaProgress.stage === "install" &&
|
|
1236
|
+
otaProgress.status === "FINISHED"
|
|
1237
|
+
|
|
1238
|
+
// progress-legacy treated a BES disconnect during active installation as the
|
|
1239
|
+
// expected power cycle even when its terminal FINISHED event was lost. Keep
|
|
1240
|
+
// that fallback only for the sticky pre-37 profile selected when the flow began.
|
|
1241
|
+
const legacyInstallInProgress =
|
|
1242
|
+
legacySession &&
|
|
1243
|
+
((otaStatus?.stepType === "bes" && otaStatus.phase === "install" && otaStatus.status === "in_progress") ||
|
|
1244
|
+
(otaProgress?.currentUpdate === "bes" &&
|
|
1245
|
+
otaProgress.stage === "install" &&
|
|
1246
|
+
(otaProgress.status === "STARTED" || otaProgress.status === "PROGRESS")))
|
|
1247
|
+
return statusReadyToRestart || progressReadyToRestart || legacyInstallInProgress
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
/** Complete only after a protocol-authorized BES restart traverses disconnect -> reconnect. */
|
|
1251
|
+
private completeBesRestartRecovery(): void {
|
|
1252
|
+
if (!this.besRestartDisconnectObserved || this.besRestartRecovery !== "awaiting") return
|
|
1253
|
+
console.log("[OTA_PROGRESS] BES restart reconnected — completing by reboot edge")
|
|
1254
|
+
this.clearPerStepTimers()
|
|
1255
|
+
this.setErrorMsg("")
|
|
1256
|
+
this.besRestartRecovery = "complete"
|
|
1257
|
+
this.emitInternalChange()
|
|
1258
|
+
}
|
|
1259
|
+
|
|
863
1260
|
/**
|
|
864
1261
|
* Legacy MTK install stall simulation (WP 8C-e), ported verbatim: the MTK system
|
|
865
1262
|
* install typically stalls around 49-50%. Every real event restarts stall detection;
|
|
@@ -935,17 +1332,54 @@ class OtaInstallCoordinator {
|
|
|
935
1332
|
sawReconnectEdge: this.sawReconnectEdge,
|
|
936
1333
|
legacyApkSettleHold: this.legacyApkSettleHold,
|
|
937
1334
|
apkCompletedViaBuildIncrease: this.apkCompletedViaBuildIncrease,
|
|
1335
|
+
besRestartRecovery: this.besRestartRecovery,
|
|
1336
|
+
versionChangeConverged: this.versionChangeConverged,
|
|
1337
|
+
versionChangeSession: this.versionChangeSession,
|
|
938
1338
|
})
|
|
939
1339
|
}
|
|
940
1340
|
|
|
941
1341
|
/**
|
|
942
|
-
*
|
|
1342
|
+
* During an APK install the package installer kills and restarts the glasses
|
|
1343
|
+
* process, and the new process's completion push can lose its startup race
|
|
1344
|
+
* against the UART transport (incident rep_01KY31HEMTSBSMK8DVMNXJ5XGG: the
|
|
1345
|
+
* phone sat on a stale "install in_progress 0%" until the stall watchdog
|
|
1346
|
+
* failed a successful update). The persisted session on the glasses always
|
|
1347
|
+
* knows the truth, so poll ota_query_status on the keepalive tick while an
|
|
1348
|
+
* apk step sits in its install phase — the reply either confirms progress or
|
|
1349
|
+
* delivers the missed completion. Gated to the apk install phase so a stray
|
|
1350
|
+
* idle reply can't disturb an active download; legacy (< 37) builds ignore
|
|
1351
|
+
* the query, which is harmless.
|
|
1352
|
+
*
|
|
1353
|
+
* The native query pends up to 15s and rejects a concurrent call with
|
|
1354
|
+
* request_in_flight — longer than the 10s tick — so exactly one poll is kept
|
|
1355
|
+
* in flight (the glasses being slow to answer IS the silent-restart window
|
|
1356
|
+
* this poll exists for) and rejections are swallowed: the next tick retries.
|
|
1357
|
+
*/
|
|
1358
|
+
private maybePollApkInstallStatus(): void {
|
|
1359
|
+
if (this.isInVersionChangeDetour()) return
|
|
1360
|
+
if (this.apkInstallPollInFlight) return
|
|
1361
|
+
const s = useGlassesStore.getState().otaStatus
|
|
1362
|
+
if (s?.stepType === "apk" && s.phase === "install" && s.status === "in_progress") {
|
|
1363
|
+
this.apkInstallPollInFlight = true
|
|
1364
|
+
void BluetoothSdk.queryOtaStatus()
|
|
1365
|
+
.catch(() => {})
|
|
1366
|
+
.finally(() => {
|
|
1367
|
+
this.apkInstallPollInFlight = false
|
|
1368
|
+
})
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
/**
|
|
1373
|
+
* "Glasses are talking to us" — suppresses retry if the native request later rejects.
|
|
943
1374
|
* Important: this does NOT clear the stuck-at-zero watchdog; that one fires
|
|
944
1375
|
* on real progress > 0% (see {@link onFirstNonZeroProgress}).
|
|
945
1376
|
*/
|
|
946
1377
|
private onFirstActivity(): void {
|
|
947
1378
|
if (this.hasFirstActivity) return
|
|
948
1379
|
this.hasFirstActivity = true
|
|
1380
|
+
if (this.otaStartOwnership?.outcome === "pending") {
|
|
1381
|
+
this.otaStartOwnership.outcome = "acknowledged"
|
|
1382
|
+
}
|
|
949
1383
|
this.clearRetryTimeout()
|
|
950
1384
|
}
|
|
951
1385
|
|
|
@@ -968,7 +1402,8 @@ class OtaInstallCoordinator {
|
|
|
968
1402
|
// Legacy-shape check happens once, when the session cap is armed at the first
|
|
969
1403
|
// ota_start send (WP 8C-b): old builds get the padded legacy cap for the whole
|
|
970
1404
|
// session; a session that starts unified keeps the unified cap.
|
|
971
|
-
const globalTimeoutMs =
|
|
1405
|
+
const globalTimeoutMs =
|
|
1406
|
+
this.versionChangeSession || this.isLegacySessionShapeNow() ? LEGACY_GLOBAL_OTA_TIMEOUT_MS : GLOBAL_OTA_TIMEOUT_MS
|
|
972
1407
|
this.globalTimeout = setTimeout(() => {
|
|
973
1408
|
this.globalTimeout = null
|
|
974
1409
|
this.globalTimeoutStarted = false
|
|
@@ -984,37 +1419,14 @@ class OtaInstallCoordinator {
|
|
|
984
1419
|
}, globalTimeoutMs)
|
|
985
1420
|
}
|
|
986
1421
|
|
|
987
|
-
private
|
|
988
|
-
this.clearRetryTimeout()
|
|
1422
|
+
private armStuckWatchdogOnly(): void {
|
|
989
1423
|
this.clearStuckTimeout()
|
|
990
1424
|
|
|
991
1425
|
// Durations resolved at arm time (WP 8C-b): legacy-shaped sessions get the
|
|
992
1426
|
// padded values progress-legacy.tsx used for old (< 37) builds.
|
|
993
1427
|
const legacySession = this.isLegacySessionShapeNow()
|
|
994
|
-
const retryIntervalMs = legacySession ? LEGACY_RETRY_INTERVAL_MS : RETRY_INTERVAL_MS
|
|
995
1428
|
const stuckTimeoutMs = legacySession ? LEGACY_DOWNLOAD_STUCK_TIMEOUT_MS : DOWNLOAD_STUCK_TIMEOUT_MS
|
|
996
1429
|
|
|
997
|
-
this.retryTimeout = setTimeout(() => {
|
|
998
|
-
this.retryTimeout = null
|
|
999
|
-
if (this.hasReceivedAck || this.hasFirstActivity) return
|
|
1000
|
-
if (this.computeDisplayStateNow() !== "starting") return
|
|
1001
|
-
if (this.retryCount < MAX_RETRIES - 1) {
|
|
1002
|
-
this.retryCount += 1
|
|
1003
|
-
this.hasReceivedAck = false
|
|
1004
|
-
console.log(
|
|
1005
|
-
`[OTA_PROGRESS] watchdog: no ack in ${retryIntervalMs}ms, retrying ota_start (attempt ${this.retryCount})`,
|
|
1006
|
-
)
|
|
1007
|
-
void this.sendOtaStartWithWatchdogs(true)
|
|
1008
|
-
.then(() => {
|
|
1009
|
-
this.armAckAndStuckWatchdogsOnly()
|
|
1010
|
-
})
|
|
1011
|
-
.catch(() => {})
|
|
1012
|
-
} else {
|
|
1013
|
-
console.log(`[OTA_PROGRESS] watchdog: ota_start ack never received after ${MAX_RETRIES} attempts, failing`)
|
|
1014
|
-
this.setErrorMsg(OtaProgressMessages.noAckResponse)
|
|
1015
|
-
}
|
|
1016
|
-
}, retryIntervalMs)
|
|
1017
|
-
|
|
1018
1430
|
this.stuckTimeout = setTimeout(() => {
|
|
1019
1431
|
this.stuckTimeout = null
|
|
1020
1432
|
const d = this.computeDisplayStateNow()
|
|
@@ -1030,26 +1442,130 @@ class OtaInstallCoordinator {
|
|
|
1030
1442
|
}, stuckTimeoutMs)
|
|
1031
1443
|
}
|
|
1032
1444
|
|
|
1033
|
-
|
|
1445
|
+
/** Arm the 0%-stuck watchdog only when this logical session has none. */
|
|
1446
|
+
private maybeArmStuckWatchdog(): void {
|
|
1447
|
+
if (this.stuckTimeout || this.hasFirstNonZeroProgress) return
|
|
1448
|
+
this.armStuckWatchdogOnly()
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
private sendOtaStartWithWatchdogs(): Promise<void> {
|
|
1452
|
+
// INVARIANT BACKSTOP — not normal control flow. Every entry point that can drive the
|
|
1453
|
+
// glasses (connect-edge, query fallback, retry, mount) carries its own detour gate with the
|
|
1454
|
+
// right behavior for that site; this final check only exists so that a FUTURE entry point
|
|
1455
|
+
// that forgets its gate degrades to a refused send + loud log instead of a corrupted
|
|
1456
|
+
// transaction (an ota_start mid-detour starts a parallel install on the factory build and
|
|
1457
|
+
// can trigger a second handoff). If this ever fires, a missed site gate exists — fix it.
|
|
1458
|
+
if (this.isInVersionChangeDetour()) {
|
|
1459
|
+
console.error(
|
|
1460
|
+
"[OTA_PROGRESS] BACKSTOP: refused ota_start during a latched version-change detour — a caller is missing its detour gate",
|
|
1461
|
+
)
|
|
1462
|
+
return Promise.resolve()
|
|
1463
|
+
}
|
|
1464
|
+
const existingOwnership = this.otaStartOwnership
|
|
1465
|
+
if (existingOwnership?.outcome === "pending") {
|
|
1466
|
+
this.maybeStartGlobalTimeout()
|
|
1467
|
+
if (this.selectedTransport === "wifi" || this.hotspotManifestUrl) this.maybeArmStuckWatchdog()
|
|
1468
|
+
console.log("[OTA_PROGRESS] ota_start already pending — adopting existing native request")
|
|
1469
|
+
return existingOwnership.promise
|
|
1470
|
+
}
|
|
1471
|
+
if (existingOwnership?.outcome === "acknowledged") {
|
|
1472
|
+
this.maybeStartGlobalTimeout()
|
|
1473
|
+
this.maybeArmStuckWatchdog()
|
|
1474
|
+
console.log("[OTA_PROGRESS] ota_start already acknowledged — refusing duplicate start")
|
|
1475
|
+
return Promise.resolve()
|
|
1476
|
+
}
|
|
1477
|
+
if (existingOwnership?.outcome === "rejected" && !this.prepareFreshOtaStartAttempt()) {
|
|
1478
|
+
return Promise.resolve()
|
|
1479
|
+
}
|
|
1034
1480
|
this.maybeStartGlobalTimeout()
|
|
1035
|
-
this.
|
|
1036
|
-
|
|
1481
|
+
if (this.selectedTransport === "wifi" || this.hotspotManifestUrl) this.maybeArmStuckWatchdog()
|
|
1482
|
+
|
|
1483
|
+
const ownership: OtaStartOwnership = {
|
|
1484
|
+
outcome: "pending",
|
|
1485
|
+
promise: Promise.resolve(),
|
|
1486
|
+
}
|
|
1487
|
+
ownership.promise = this.performOtaStart(ownership)
|
|
1488
|
+
this.otaStartOwnership = ownership
|
|
1489
|
+
return ownership.promise
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
private async performOtaStart(ownership: OtaStartOwnership): Promise<void> {
|
|
1493
|
+
let nativeStartAttempted = false
|
|
1037
1494
|
try {
|
|
1038
1495
|
const state = useGlassesStore.getState()
|
|
1039
|
-
|
|
1040
|
-
|
|
1496
|
+
let otaVersionUrl = resolveOtaManifestUrl(state.otaVersionUrl, state.buildNumber)
|
|
1497
|
+
if (this.selectedTransport === "hotspot") {
|
|
1498
|
+
if (!this.hotspotManifestUrl) {
|
|
1499
|
+
if (!this.preparedCheckResult) {
|
|
1500
|
+
throw new Error("No selected OTA check is available for hotspot staging")
|
|
1501
|
+
}
|
|
1502
|
+
this.hotspotManifestUrl = await hotspotOtaTransport.prepare(this.preparedCheckResult, (progress) => {
|
|
1503
|
+
this.hotspotPhase = progress.phase
|
|
1504
|
+
this.hotspotArtifactPercent = progress.artifact?.artifactPercent ?? null
|
|
1505
|
+
this.emitInternalChange()
|
|
1506
|
+
})
|
|
1507
|
+
}
|
|
1508
|
+
otaVersionUrl = this.hotspotManifestUrl
|
|
1509
|
+
this.maybeArmStuckWatchdog()
|
|
1510
|
+
}
|
|
1511
|
+
if (!otaVersionUrl) {
|
|
1512
|
+
throw new Error("OTA is disabled because this build has no immutable manifest pin")
|
|
1513
|
+
}
|
|
1514
|
+
console.log(`[OTA_PROGRESS] sending ota_start with ${this.selectedTransport} manifest URL: ${otaVersionUrl}`)
|
|
1515
|
+
nativeStartAttempted = true
|
|
1041
1516
|
await BluetoothSdk.startOtaUpdate(otaVersionUrl)
|
|
1517
|
+
if (this.otaStartOwnership !== ownership) return
|
|
1518
|
+
// The public SDK promise resolves only from ota_start_ack. Treat that
|
|
1519
|
+
// resolution as the acknowledgement even if the parallel event dispatch
|
|
1520
|
+
// reaches the coordinator later (or is dropped by a remount).
|
|
1521
|
+
this.handleAck()
|
|
1042
1522
|
} catch (err) {
|
|
1043
1523
|
console.warn("[OTA_PROGRESS] sendOtaStart threw", err)
|
|
1044
|
-
|
|
1524
|
+
|
|
1525
|
+
if (this.otaStartOwnership !== ownership) return
|
|
1526
|
+
|
|
1527
|
+
if (this.selectedTransport === "hotspot" && !nativeStartAttempted) {
|
|
1528
|
+
ownership.outcome = "rejected"
|
|
1529
|
+
this.clearStuckTimeout()
|
|
1530
|
+
await this.teardownHotspotTransport()
|
|
1531
|
+
if (this.attached) this.setErrorMsg(hotspotPreflightErrorMessage(err))
|
|
1532
|
+
return
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
// A received ota_status/legacy progress event, or the exact legacy APK
|
|
1536
|
+
// build-number proof, proves that this logical start was accepted. Its
|
|
1537
|
+
// application-level ack may be lost while the update continues; never
|
|
1538
|
+
// create another transaction or fail a live, restarting, or completed
|
|
1539
|
+
// session because that stale promise timed out.
|
|
1540
|
+
if (this.hasFirstActivity || this.apkCompletedViaBuildIncrease) {
|
|
1541
|
+
ownership.outcome = "acknowledged"
|
|
1542
|
+
console.log("[OTA_PROGRESS] ota_start rejection arrived after acceptance proof — ignoring stale rejection")
|
|
1543
|
+
return
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
ownership.outcome = "rejected"
|
|
1547
|
+
if (!this.attached) return
|
|
1045
1548
|
this.clearStuckTimeout()
|
|
1549
|
+
if (!isGlassesConnected(useGlassesStore.getState().connection)) {
|
|
1550
|
+
console.log(
|
|
1551
|
+
"[OTA_PROGRESS] ota_start request ended while disconnected; deferring retry to reconnect arbitration",
|
|
1552
|
+
)
|
|
1553
|
+
return
|
|
1554
|
+
}
|
|
1046
1555
|
if (this.retryCount < MAX_RETRIES - 1) {
|
|
1047
|
-
|
|
1048
|
-
this.retryCount += 1
|
|
1049
|
-
}
|
|
1556
|
+
this.retryCount += 1
|
|
1050
1557
|
const retryIntervalMs = this.isLegacySessionShapeNow() ? LEGACY_RETRY_INTERVAL_MS : RETRY_INTERVAL_MS
|
|
1558
|
+
console.log(
|
|
1559
|
+
`[OTA_PROGRESS] ota_start request ended without ack or activity; retrying after ${retryIntervalMs}ms (attempt ${
|
|
1560
|
+
this.retryCount + 1
|
|
1561
|
+
}/${MAX_RETRIES})`,
|
|
1562
|
+
)
|
|
1051
1563
|
this.retryTimeout = setTimeout(() => {
|
|
1052
1564
|
this.retryTimeout = null
|
|
1565
|
+
if (!isGlassesConnected(useGlassesStore.getState().connection)) {
|
|
1566
|
+
console.log("[OTA_PROGRESS] ota_start retry paused while disconnected; reconnect arbitration will resume")
|
|
1567
|
+
return
|
|
1568
|
+
}
|
|
1053
1569
|
void this.sendOtaStartWithWatchdogs()
|
|
1054
1570
|
}, retryIntervalMs)
|
|
1055
1571
|
} else {
|
|
@@ -1059,18 +1575,28 @@ class OtaInstallCoordinator {
|
|
|
1059
1575
|
}
|
|
1060
1576
|
}
|
|
1061
1577
|
|
|
1578
|
+
private async teardownHotspotTransport(): Promise<void> {
|
|
1579
|
+
if (this.selectedTransport !== "hotspot" && !this.hotspotManifestUrl) return
|
|
1580
|
+
await hotspotOtaTransport.teardown()
|
|
1581
|
+
this.hotspotManifestUrl = null
|
|
1582
|
+
this.hotspotPhase = "idle"
|
|
1583
|
+
this.hotspotArtifactPercent = null
|
|
1584
|
+
this.emitInternalChange()
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1062
1587
|
/**
|
|
1063
1588
|
* After sending ota_query_status, wait QUERY_REPLY_TIMEOUT_MS for the glasses
|
|
1064
|
-
* to reply with a useful ota_status.
|
|
1065
|
-
*
|
|
1066
|
-
*
|
|
1067
|
-
*
|
|
1589
|
+
* to reply with a useful ota_status. A unified session may start again only
|
|
1590
|
+
* after an explicit "idle" reply, or after a locally rejected ota_start has
|
|
1591
|
+
* produced no activity and no authoritative reply across reconnect. Legacy
|
|
1592
|
+
* builds ignore ota_query_status, so their established timeout fallback
|
|
1593
|
+
* remains the compatibility path.
|
|
1068
1594
|
*
|
|
1069
1595
|
* Cleared as soon as a non-idle otaStatus or any otaProgress lands in the
|
|
1070
1596
|
* store (see the reaction block above). An idle reply intentionally does NOT
|
|
1071
1597
|
* cancel the fallback, so reconnects against a wiped/lost session still recover.
|
|
1072
1598
|
*/
|
|
1073
|
-
private armQueryReplyFallback(reason: "reconnect" | "initial-mount"): void {
|
|
1599
|
+
private armQueryReplyFallback(reason: "reconnect" | "initial-mount" | "retry"): void {
|
|
1074
1600
|
this.clearQueryReplyTimeout()
|
|
1075
1601
|
this.queryReplyTimeout = setTimeout(() => {
|
|
1076
1602
|
this.queryReplyTimeout = null
|
|
@@ -1081,21 +1607,24 @@ class OtaInstallCoordinator {
|
|
|
1081
1607
|
// whatever legacy-shaped status/progress sits in the store is a pre-query
|
|
1082
1608
|
// leftover, not a reply — a NEW legacy event would have cleared this timer
|
|
1083
1609
|
// through the reaction pass. Only a unified reply suppresses the fallback.
|
|
1084
|
-
const legacySession =
|
|
1085
|
-
state.otaStatus,
|
|
1086
|
-
state.otaProgress,
|
|
1087
|
-
this.initialBuildNumber || state.buildNumber,
|
|
1088
|
-
)
|
|
1610
|
+
const legacySession = this.isLegacySessionShapeNow()
|
|
1089
1611
|
if (!legacySession && hasRecoveringOtaReply(state.otaStatus, state.otaProgress)) return
|
|
1612
|
+
const explicitIdle = state.otaStatus?.status === "idle"
|
|
1613
|
+
const rejectedStart = this.otaStartOwnership?.outcome === "rejected"
|
|
1614
|
+
if (!legacySession && !explicitIdle && !rejectedStart) {
|
|
1615
|
+
console.log(
|
|
1616
|
+
`[OTA_PROGRESS] watchdog: ota_query_status got no authoritative idle reply in ${QUERY_REPLY_TIMEOUT_MS}ms (reason=${reason}); preserving the existing session`,
|
|
1617
|
+
)
|
|
1618
|
+
return
|
|
1619
|
+
}
|
|
1090
1620
|
// Don't fire if we've left the active phase (e.g. user backed out, error overlay).
|
|
1091
1621
|
if (isTerminalForWatchdog(this.computeDisplayStateNow())) return
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
)
|
|
1622
|
+
let fallbackCause = "legacy query timeout"
|
|
1623
|
+
if (explicitIdle) fallbackCause = "idle ota_status"
|
|
1624
|
+
else if (rejectedStart) fallbackCause = "rejected ota_start with no recovery reply"
|
|
1625
|
+
console.log(`[OTA_PROGRESS] watchdog: ${fallbackCause} (reason=${reason}), falling back to ota_start`)
|
|
1095
1626
|
this.retryCount = 0
|
|
1096
|
-
this.
|
|
1097
|
-
this.hasFirstNonZeroProgress = false
|
|
1098
|
-
this.hasReceivedAck = false
|
|
1627
|
+
if (this.otaStartOwnership?.outcome !== "pending" && !this.prepareFreshOtaStartAttempt()) return
|
|
1099
1628
|
void this.sendOtaStartWithWatchdogs()
|
|
1100
1629
|
}, QUERY_REPLY_TIMEOUT_MS)
|
|
1101
1630
|
}
|
|
@@ -1131,13 +1660,6 @@ class OtaInstallCoordinator {
|
|
|
1131
1660
|
this.globalTimeoutStarted = false
|
|
1132
1661
|
}
|
|
1133
1662
|
|
|
1134
|
-
private clearPostApkDelay(): void {
|
|
1135
|
-
if (this.postApkDelay) {
|
|
1136
|
-
clearTimeout(this.postApkDelay)
|
|
1137
|
-
this.postApkDelay = null
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
|
|
1141
1663
|
private clearPingInterval(): void {
|
|
1142
1664
|
if (this.pingInterval) {
|
|
1143
1665
|
clearInterval(this.pingInterval)
|
|
@@ -1159,6 +1681,13 @@ class OtaInstallCoordinator {
|
|
|
1159
1681
|
}
|
|
1160
1682
|
}
|
|
1161
1683
|
|
|
1684
|
+
private clearRestartRecoveryTimeout(): void {
|
|
1685
|
+
if (this.restartRecoveryTimeout) {
|
|
1686
|
+
clearTimeout(this.restartRecoveryTimeout)
|
|
1687
|
+
this.restartRecoveryTimeout = null
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1162
1691
|
private clearLegacyApkSettleTimer(): void {
|
|
1163
1692
|
if (this.legacyApkSettleTimer) {
|
|
1164
1693
|
clearTimeout(this.legacyApkSettleTimer)
|
|
@@ -1188,7 +1717,6 @@ class OtaInstallCoordinator {
|
|
|
1188
1717
|
this.clearRetryTimeout()
|
|
1189
1718
|
this.clearStuckTimeout()
|
|
1190
1719
|
this.clearProgressTimeout()
|
|
1191
|
-
this.clearPostApkDelay()
|
|
1192
1720
|
this.clearQueryReplyTimeout()
|
|
1193
1721
|
}
|
|
1194
1722
|
|
|
@@ -1198,6 +1726,7 @@ class OtaInstallCoordinator {
|
|
|
1198
1726
|
this.clearPingInterval()
|
|
1199
1727
|
this.clearLegacyApkSettleTimer()
|
|
1200
1728
|
this.clearMtkSimulationTimers()
|
|
1729
|
+
this.clearRestartRecoveryTimeout()
|
|
1201
1730
|
}
|
|
1202
1731
|
}
|
|
1203
1732
|
|