@mentra/engine 0.1.0-beta.0 → 0.1.0-dev.1
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 +42 -1
- package/build/engine.d.ts +11 -3
- package/build/engine.d.ts.map +1 -1
- package/build/engine.js +7 -2
- package/build/engine.js.map +1 -1
- package/build/facades/dev.d.ts.map +1 -1
- package/build/facades/dev.js +9 -5
- package/build/facades/dev.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 +1 -0
- package/build/facades/glasses.d.ts.map +1 -1
- package/build/facades/glasses.js +19 -4
- 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 +11 -2
- package/build/facades/ota.d.ts.map +1 -1
- package/build/facades/ota.js +19 -4
- package/build/facades/ota.js.map +1 -1
- package/build/facades/pairing.d.ts +4 -0
- package/build/facades/pairing.d.ts.map +1 -1
- package/build/facades/pairing.js +10 -8
- package/build/facades/pairing.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/index.d.ts +6 -3
- package/build/index.d.ts.map +1 -1
- package/build/index.js +4 -2
- package/build/index.js.map +1 -1
- package/build/internal.d.ts +2 -2
- package/build/internal.d.ts.map +1 -1
- package/build/internal.js +1 -1
- package/build/internal.js.map +1 -1
- package/build/react/MentraLiveOtaFlow.d.ts +38 -0
- package/build/react/MentraLiveOtaFlow.d.ts.map +1 -0
- package/build/react/MentraLiveOtaFlow.js +528 -0
- package/build/react/MentraLiveOtaFlow.js.map +1 -0
- package/build/react/index.d.ts +2 -0
- package/build/react/index.d.ts.map +1 -0
- package/build/react/index.js +2 -0
- package/build/react/index.js.map +1 -0
- package/build/react/useEngineSnapshot.d.ts +2 -0
- package/build/react/useEngineSnapshot.d.ts.map +1 -0
- package/build/react/useEngineSnapshot.js +14 -0
- package/build/react/useEngineSnapshot.js.map +1 -0
- package/build/runtime/bootstrap.d.ts +24 -1
- package/build/runtime/bootstrap.d.ts.map +1 -1
- package/build/runtime/bootstrap.js +17 -0
- package/build/runtime/bootstrap.js.map +1 -1
- package/build/runtime/scanQrSeam.d.ts +7 -0
- package/build/runtime/scanQrSeam.d.ts.map +1 -0
- package/build/runtime/scanQrSeam.js +16 -0
- package/build/runtime/scanQrSeam.js.map +1 -0
- package/build/services/AppRegistry.d.ts +18 -8
- package/build/services/AppRegistry.d.ts.map +1 -1
- package/build/services/AppRegistry.js +44 -45
- package/build/services/AppRegistry.js.map +1 -1
- package/build/services/AudioCloudUplink.d.ts +9 -3
- package/build/services/AudioCloudUplink.d.ts.map +1 -1
- package/build/services/AudioCloudUplink.js +9 -3
- package/build/services/AudioCloudUplink.js.map +1 -1
- package/build/services/AudioPlaybackService.d.ts +9 -1
- package/build/services/AudioPlaybackService.d.ts.map +1 -1
- package/build/services/AudioPlaybackService.js +77 -32
- package/build/services/AudioPlaybackService.js.map +1 -1
- package/build/services/BlobStore.d.ts +2 -1
- package/build/services/BlobStore.d.ts.map +1 -1
- package/build/services/BlobStore.js +17 -9
- package/build/services/BlobStore.js.map +1 -1
- package/build/services/CloudAudioSubscriptionSync.d.ts.map +1 -1
- package/build/services/CloudAudioSubscriptionSync.js +13 -1
- package/build/services/CloudAudioSubscriptionSync.js.map +1 -1
- package/build/services/CloudClientService.d.ts +15 -0
- package/build/services/CloudClientService.d.ts.map +1 -1
- package/build/services/CloudClientService.js +75 -0
- package/build/services/CloudClientService.js.map +1 -1
- package/build/services/ForegroundLocationPermission.d.ts +13 -0
- package/build/services/ForegroundLocationPermission.d.ts.map +1 -0
- package/build/services/ForegroundLocationPermission.js +12 -0
- package/build/services/ForegroundLocationPermission.js.map +1 -0
- package/build/services/GlassesSettingsSync.d.ts.map +1 -1
- package/build/services/GlassesSettingsSync.js +9 -3
- package/build/services/GlassesSettingsSync.js.map +1 -1
- package/build/services/GlassesStatusProjection.d.ts +2 -1
- package/build/services/GlassesStatusProjection.d.ts.map +1 -1
- package/build/services/GlassesStatusProjection.js +15 -8
- package/build/services/GlassesStatusProjection.js.map +1 -1
- package/build/services/HotspotOtaTransport.d.ts +29 -0
- package/build/services/HotspotOtaTransport.d.ts.map +1 -0
- package/build/services/HotspotOtaTransport.js +146 -0
- package/build/services/HotspotOtaTransport.js.map +1 -0
- package/build/services/HotspotShutdown.d.ts +17 -0
- package/build/services/HotspotShutdown.d.ts.map +1 -0
- package/build/services/HotspotShutdown.js +29 -0
- package/build/services/HotspotShutdown.js.map +1 -0
- package/build/services/LocalDisplayManager.d.ts +15 -0
- package/build/services/LocalDisplayManager.d.ts.map +1 -1
- package/build/services/LocalDisplayManager.js +106 -20
- package/build/services/LocalDisplayManager.js.map +1 -1
- package/build/services/LocalMiniappRuntime.d.ts +67 -1
- package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
- package/build/services/LocalMiniappRuntime.js +486 -126
- 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/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/OtaArtifactDownloader.d.ts +65 -0
- package/build/services/OtaArtifactDownloader.d.ts.map +1 -0
- package/build/services/OtaArtifactDownloader.js +226 -0
- package/build/services/OtaArtifactDownloader.js.map +1 -0
- package/build/services/OtaAutoChain.d.ts +34 -0
- package/build/services/OtaAutoChain.d.ts.map +1 -0
- package/build/services/OtaAutoChain.js +83 -0
- package/build/services/OtaAutoChain.js.map +1 -0
- package/build/services/OtaErrorMapping.d.ts +18 -0
- package/build/services/OtaErrorMapping.d.ts.map +1 -0
- package/build/services/OtaErrorMapping.js +63 -0
- package/build/services/OtaErrorMapping.js.map +1 -0
- package/build/services/OtaInstallCoordinator.d.ts +94 -14
- package/build/services/OtaInstallCoordinator.d.ts.map +1 -1
- package/build/services/OtaInstallCoordinator.js +574 -130
- package/build/services/OtaInstallCoordinator.js.map +1 -1
- package/build/services/OtaService.d.ts.map +1 -1
- package/build/services/OtaService.js +8 -1
- package/build/services/OtaService.js.map +1 -1
- package/build/services/OtaUpdateCheckService.d.ts +38 -3
- package/build/services/OtaUpdateCheckService.d.ts.map +1 -1
- package/build/services/OtaUpdateCheckService.js +142 -22
- package/build/services/OtaUpdateCheckService.js.map +1 -1
- package/build/services/PhoneCalendarService.d.ts.map +1 -1
- package/build/services/PhoneCalendarService.js +22 -1
- package/build/services/PhoneCalendarService.js.map +1 -1
- package/build/services/PhoneCameraFovCoordinator.d.ts +2 -0
- package/build/services/PhoneCameraFovCoordinator.d.ts.map +1 -1
- package/build/services/PhoneCameraFovCoordinator.js +14 -0
- package/build/services/PhoneCameraFovCoordinator.js.map +1 -1
- 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 +10 -3
- package/build/services/PhonePhotoCoordinator.d.ts.map +1 -1
- package/build/services/PhonePhotoCoordinator.js +21 -3
- 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/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/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 +3 -0
- package/build/services/asg/asgCameraApi.d.ts.map +1 -1
- package/build/services/asg/asgCameraApi.js +72 -2
- package/build/services/asg/asgCameraApi.js.map +1 -1
- package/build/services/asg/gallerySyncService.d.ts.map +1 -1
- package/build/services/asg/gallerySyncService.js +26 -14
- package/build/services/asg/gallerySyncService.js.map +1 -1
- package/build/services/asg/localNetworkTransport.d.ts +2 -2
- package/build/services/asg/localNetworkTransport.d.ts.map +1 -1
- package/build/services/asg/localNetworkTransport.js +6 -6
- package/build/services/asg/localNetworkTransport.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/glassesClockSync.js +2 -2
- package/build/services/glassesClockSync.js.map +1 -1
- package/build/services/manifestActions.d.ts +7 -0
- package/build/services/manifestActions.d.ts.map +1 -0
- package/build/services/manifestActions.js +25 -0
- package/build/services/manifestActions.js.map +1 -0
- package/build/services/otaDisplayState.d.ts +22 -11
- 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 +15 -3
- package/build/services/otaInstallPolicy.d.ts.map +1 -1
- package/build/services/otaInstallPolicy.js +24 -3
- package/build/services/otaInstallPolicy.js.map +1 -1
- package/build/services/otaManifestUrl.d.ts +2 -0
- package/build/services/otaManifestUrl.d.ts.map +1 -0
- package/build/services/{asgOtaVersionUrl.js → otaManifestUrl.js} +15 -5
- package/build/services/otaManifestUrl.js.map +1 -0
- package/build/services/slimStreamStatus.d.ts +11 -0
- package/build/services/slimStreamStatus.d.ts.map +1 -1
- package/build/services/slimStreamStatus.js +14 -4
- package/build/services/slimStreamStatus.js.map +1 -1
- package/build/stores/apps.d.ts.map +1 -1
- package/build/stores/apps.js +11 -0
- package/build/stores/apps.js.map +1 -1
- package/build/stores/bluetoothSettingKeys.d.ts.map +1 -1
- package/build/stores/bluetoothSettingKeys.js +2 -0
- package/build/stores/bluetoothSettingKeys.js.map +1 -1
- package/build/stores/gallerySync.d.ts +2 -0
- package/build/stores/gallerySync.d.ts.map +1 -1
- package/build/stores/gallerySync.js +2 -0
- package/build/stores/gallerySync.js.map +1 -1
- package/build/stores/glasses.d.ts.map +1 -1
- package/build/stores/glasses.js +3 -0
- package/build/stores/glasses.js.map +1 -1
- package/build/stores/settings.d.ts +17 -0
- package/build/stores/settings.d.ts.map +1 -1
- package/build/stores/settings.js +152 -19
- package/build/stores/settings.js.map +1 -1
- package/build/types/applet.d.ts +2 -0
- package/build/types/applet.d.ts.map +1 -1
- package/build/types/applet.js.map +1 -1
- package/build/types/enums.d.ts +1 -0
- package/build/types/enums.d.ts.map +1 -1
- package/build/types/enums.js +1 -0
- package/build/types/enums.js.map +1 -1
- package/build/types/hardware.d.ts +1 -0
- package/build/types/hardware.d.ts.map +1 -1
- package/build/types/hardware.js +14 -0
- package/build/types/hardware.js.map +1 -1
- package/build/utils/configuredDevHost.d.ts +6 -0
- package/build/utils/configuredDevHost.d.ts.map +1 -0
- package/build/utils/configuredDevHost.js +19 -0
- package/build/utils/configuredDevHost.js.map +1 -0
- package/build/utils/devMiniappLaunch.d.ts +19 -8
- package/build/utils/devMiniappLaunch.d.ts.map +1 -1
- package/build/utils/devMiniappLaunch.js +144 -21
- package/build/utils/devMiniappLaunch.js.map +1 -1
- package/build/utils/diagnosticContext.d.ts.map +1 -1
- package/build/utils/diagnosticContext.js +15 -17
- package/build/utils/diagnosticContext.js.map +1 -1
- package/build/utils/hardware/hardware.d.ts.map +1 -1
- package/build/utils/hardware/hardware.js +7 -3
- package/build/utils/hardware/hardware.js.map +1 -1
- package/build/utils/miniappDiagnostics.d.ts +64 -0
- package/build/utils/miniappDiagnostics.d.ts.map +1 -0
- package/build/utils/miniappDiagnostics.js +173 -0
- package/build/utils/miniappDiagnostics.js.map +1 -0
- package/build/utils/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 +27 -18
- package/src/assets/silence.wav +0 -0
- package/src/engine.ts +7 -2
- package/src/facades/dev.ts +9 -5
- package/src/facades/gallery.ts +1 -1
- package/src/facades/glasses.ts +17 -4
- package/src/facades/glassesSettings.ts +5 -0
- package/src/facades/miniapps.ts +7 -0
- package/src/facades/ota.ts +19 -4
- package/src/facades/pairing.ts +11 -7
- package/src/facades/phoneNotifications.ts +4 -13
- package/src/index.ts +26 -2
- package/src/internal.ts +2 -1
- package/src/react/MentraLiveOtaFlow.tsx +856 -0
- package/src/react/index.ts +7 -0
- package/src/react/useEngineSnapshot.ts +16 -0
- package/src/runtime/bootstrap.ts +33 -1
- package/src/runtime/scanQrSeam.ts +20 -0
- package/src/services/AppRegistry.ts +72 -48
- package/src/services/AudioCloudUplink.ts +9 -3
- package/src/services/AudioPlaybackService.ts +94 -34
- package/src/services/BlobStore.ts +22 -9
- package/src/services/CloudAudioSubscriptionSync.ts +13 -1
- package/src/services/CloudClientService.ts +74 -0
- package/src/services/ForegroundLocationPermission.ts +24 -0
- package/src/services/GlassesSettingsSync.ts +9 -3
- package/src/services/GlassesStatusProjection.ts +16 -8
- package/src/services/HotspotOtaTransport.ts +184 -0
- package/src/services/HotspotShutdown.ts +41 -0
- package/src/services/LocalDisplayManager.ts +112 -21
- package/src/services/LocalMiniappRuntime.ts +576 -130
- package/src/services/LocalMiniappStorage.ts +82 -0
- package/src/services/LocalMiniappUserIdentity.ts +46 -0
- package/src/services/MicStateCoordinator.ts +169 -4
- package/src/services/MiniappLauncher.ts +30 -3
- package/src/services/MiniappLiveness.ts +24 -0
- package/src/services/OtaArtifactDownloader.ts +312 -0
- package/src/services/OtaAutoChain.ts +106 -0
- package/src/services/OtaErrorMapping.ts +80 -0
- package/src/services/OtaInstallCoordinator.ts +645 -143
- package/src/services/OtaService.ts +11 -5
- package/src/services/OtaUpdateCheckService.ts +180 -20
- package/src/services/PhoneCalendarService.ts +27 -4
- package/src/services/PhoneCameraFovCoordinator.ts +15 -0
- package/src/services/PhoneNotificationsSync.ts +12 -10
- package/src/services/PhonePhotoCoordinator.ts +33 -7
- package/src/services/PhoneStreamCoordinator.ts +139 -25
- package/src/services/PhoneVideoCoordinator.ts +11 -0
- package/src/services/SceneRenderer.ts +16 -0
- package/src/services/SupportProfileSync.ts +218 -0
- package/src/services/TtsTextSanitizer.ts +66 -0
- package/src/services/WifiAdbRequest.ts +53 -0
- package/src/services/asg/asgCameraApi.ts +99 -8
- package/src/services/asg/gallerySyncService.ts +28 -19
- package/src/services/asg/localNetworkTransport.ts +7 -11
- package/src/services/audioPlaybackAssets.ts +8 -0
- package/src/services/glassesClockSync.ts +2 -2
- package/src/services/manifestActions.ts +30 -0
- package/src/services/otaDisplayState.ts +57 -16
- package/src/services/otaInstallPolicy.ts +29 -3
- package/src/services/{asgOtaVersionUrl.ts → otaManifestUrl.ts} +16 -4
- package/src/services/slimStreamStatus.ts +16 -5
- package/src/stores/apps.ts +12 -0
- package/src/stores/bluetoothSettingKeys.ts +2 -0
- package/src/stores/gallerySync.ts +4 -0
- package/src/stores/glasses.ts +3 -0
- package/src/stores/settings.ts +174 -21
- package/src/types/applet.ts +2 -0
- package/src/types/enums.ts +2 -1
- package/src/types/hardware.ts +17 -0
- package/src/utils/configuredDevHost.ts +17 -0
- package/src/utils/devMiniappLaunch.ts +151 -23
- package/src/utils/diagnosticContext.ts +17 -17
- package/src/utils/hardware/hardware.ts +7 -9
- package/src/utils/miniappDiagnostics.ts +226 -0
- package/src/utils/timers/timers.ts +57 -21
- package/build/services/asgOtaVersionUrl.d.ts +0 -2
- package/build/services/asgOtaVersionUrl.d.ts.map +0 -1
- package/build/services/asgOtaVersionUrl.js.map +0 -1
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import BluetoothSdk, { isEnabledHotspotStatus, MentraOtaServer } from "@mentra/bluetooth-sdk/internal";
|
|
2
|
+
// One coordinator selects the native Android or iOS staging implementation at runtime.
|
|
3
|
+
// eslint-disable-next-line react-native/split-platform-components
|
|
4
|
+
import { PermissionsAndroid, Platform } from "react-native";
|
|
5
|
+
import { cleanupArtifacts, OtaArtifactError, planArtifacts, prepareArtifacts, rewriteManifestForLocalServer, } from "./OtaArtifactDownloader";
|
|
6
|
+
import { disableHotspotWithRetry } from "./HotspotShutdown";
|
|
7
|
+
import { localNetworkTransport } from "./asg/localNetworkTransport";
|
|
8
|
+
export class HotspotOtaTransportError extends Error {
|
|
9
|
+
code;
|
|
10
|
+
constructor(code, message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.code = code;
|
|
13
|
+
this.name = "HotspotOtaTransportError";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/** Owns the single phone-side endpoint and network lease for one hotspot OTA attempt. */
|
|
17
|
+
class HotspotOtaTransport {
|
|
18
|
+
active = false;
|
|
19
|
+
prepared = [];
|
|
20
|
+
hotspotRequested = false;
|
|
21
|
+
localNetworkConnected = false;
|
|
22
|
+
serverStarted = false;
|
|
23
|
+
teardownPromise = null;
|
|
24
|
+
async prepare(checkResult, onProgress) {
|
|
25
|
+
if (this.teardownPromise)
|
|
26
|
+
await this.teardownPromise;
|
|
27
|
+
if (this.active) {
|
|
28
|
+
throw new Error("A hotspot OTA transport is already active");
|
|
29
|
+
}
|
|
30
|
+
if (!checkResult.manifestBody) {
|
|
31
|
+
throw new Error("The selected OTA check has no manifest body");
|
|
32
|
+
}
|
|
33
|
+
let phase = "downloading";
|
|
34
|
+
try {
|
|
35
|
+
await this.ensureAndroidWifiPermission();
|
|
36
|
+
onProgress?.({ phase: "downloading" });
|
|
37
|
+
this.prepared = await prepareArtifacts(planArtifacts(checkResult), (artifact) => onProgress?.({ phase: "downloading", artifact }), Platform.OS === "ios" ? this.downloadIosArtifact : undefined);
|
|
38
|
+
phase = "starting_hotspot";
|
|
39
|
+
onProgress?.({ phase: "starting_hotspot" });
|
|
40
|
+
this.hotspotRequested = true;
|
|
41
|
+
const hotspot = await BluetoothSdk.setHotspotState(true);
|
|
42
|
+
if (!isEnabledHotspotStatus(hotspot)) {
|
|
43
|
+
throw new HotspotOtaTransportError("hotspot_start_failed", "Mentra Live did not return hotspot credentials");
|
|
44
|
+
}
|
|
45
|
+
phase = "joining_hotspot";
|
|
46
|
+
onProgress?.({ phase: "joining_hotspot" });
|
|
47
|
+
const scopedAddress = await localNetworkTransport.connect(hotspot.ssid, hotspot.password);
|
|
48
|
+
this.localNetworkConnected = true;
|
|
49
|
+
let localAddress = scopedAddress;
|
|
50
|
+
if (Platform.OS === "ios") {
|
|
51
|
+
try {
|
|
52
|
+
localAddress = await MentraOtaServer.waitForWifiAddress(hotspot.localIp, 15_000);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
throw new HotspotOtaTransportError("hotspot_join_failed", error instanceof Error ? error.message : String(error));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const artifactPaths = Object.fromEntries(this.prepared.map((artifact) => [artifact.sha256, artifact.filePath]));
|
|
59
|
+
// Start exactly one native listener to learn its selected port, then atomically replace
|
|
60
|
+
// the placeholder with the immutable rewritten manifest before ota_start is sent.
|
|
61
|
+
const server = await MentraOtaServer.startOtaServer("{}", artifactPaths, localAddress);
|
|
62
|
+
this.serverStarted = true;
|
|
63
|
+
const manifest = rewriteManifestForLocalServer(checkResult.manifestBody, this.prepared, server.baseUrl);
|
|
64
|
+
const published = await MentraOtaServer.startOtaServer(manifest, artifactPaths, server.host);
|
|
65
|
+
if (published.manifestUrl !== server.manifestUrl) {
|
|
66
|
+
throw new Error("Local OTA server endpoint changed while publishing the manifest");
|
|
67
|
+
}
|
|
68
|
+
this.active = true;
|
|
69
|
+
onProgress?.({ phase: "serving" });
|
|
70
|
+
return published.manifestUrl;
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
const joined = this.localNetworkConnected;
|
|
74
|
+
await this.teardown();
|
|
75
|
+
if (error instanceof OtaArtifactError || error instanceof HotspotOtaTransportError) {
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
79
|
+
if (phase === "starting_hotspot") {
|
|
80
|
+
throw new HotspotOtaTransportError("hotspot_start_failed", message);
|
|
81
|
+
}
|
|
82
|
+
if (phase === "joining_hotspot" && !joined) {
|
|
83
|
+
throw new HotspotOtaTransportError("hotspot_join_failed", message);
|
|
84
|
+
}
|
|
85
|
+
throw new HotspotOtaTransportError("hotspot_server_failed", message);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** Android 13+ requires the nearby-WiFi runtime grant before WifiNetworkSpecifier is usable. */
|
|
89
|
+
async ensureAndroidWifiPermission() {
|
|
90
|
+
if (Platform.OS !== "android")
|
|
91
|
+
return;
|
|
92
|
+
const permission = PermissionsAndroid.PERMISSIONS.NEARBY_WIFI_DEVICES;
|
|
93
|
+
if (await PermissionsAndroid.check(permission))
|
|
94
|
+
return;
|
|
95
|
+
const result = await PermissionsAndroid.request(permission);
|
|
96
|
+
if (result !== PermissionsAndroid.RESULTS.GRANTED) {
|
|
97
|
+
throw new HotspotOtaTransportError("hotspot_wifi_permission_denied", "Nearby devices permission is required to connect to the glasses hotspot");
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
downloadIosArtifact = async (entry, destination, onProgress) => {
|
|
101
|
+
const subscription = MentraOtaServer.addListener("artifactDownloadProgress", (event) => {
|
|
102
|
+
if (event.destination === destination)
|
|
103
|
+
onProgress?.(event.bytesWritten, event.contentLength);
|
|
104
|
+
});
|
|
105
|
+
try {
|
|
106
|
+
return await MentraOtaServer.downloadArtifact(entry.url, destination);
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
subscription.remove();
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
async teardown() {
|
|
113
|
+
if (this.teardownPromise)
|
|
114
|
+
return this.teardownPromise;
|
|
115
|
+
this.teardownPromise = (async () => {
|
|
116
|
+
if (this.hotspotRequested) {
|
|
117
|
+
const hotspotStopped = await disableHotspotWithRetry(() => BluetoothSdk.setHotspotState(false), {
|
|
118
|
+
// A completed APK step has just replaced ASG. Let the new command path
|
|
119
|
+
// settle before asking it to tear down the SystemUI-owned access point.
|
|
120
|
+
initialDelayMs: this.serverStarted ? 750 : 0,
|
|
121
|
+
});
|
|
122
|
+
if (!hotspotStopped) {
|
|
123
|
+
console.warn("[OTA_PROGRESS] glasses hotspot shutdown was not confirmed after bounded retries");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (this.serverStarted)
|
|
127
|
+
await MentraOtaServer.stopOtaServer().catch(() => { });
|
|
128
|
+
if (this.localNetworkConnected)
|
|
129
|
+
await localNetworkTransport.disconnect().catch(() => { });
|
|
130
|
+
await cleanupArtifacts().catch(() => { });
|
|
131
|
+
this.prepared = [];
|
|
132
|
+
this.active = false;
|
|
133
|
+
this.serverStarted = false;
|
|
134
|
+
this.localNetworkConnected = false;
|
|
135
|
+
this.hotspotRequested = false;
|
|
136
|
+
})();
|
|
137
|
+
try {
|
|
138
|
+
await this.teardownPromise;
|
|
139
|
+
}
|
|
140
|
+
finally {
|
|
141
|
+
this.teardownPromise = null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
export const hotspotOtaTransport = new HotspotOtaTransport();
|
|
146
|
+
//# sourceMappingURL=HotspotOtaTransport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HotspotOtaTransport.js","sourceRoot":"","sources":["../../src/services/HotspotOtaTransport.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,EAAE,EAAC,sBAAsB,EAAE,eAAe,EAAC,MAAM,gCAAgC,CAAA;AACpG,uFAAuF;AACvF,kEAAkE;AAClE,OAAO,EAAC,kBAAkB,EAAE,QAAQ,EAAC,MAAM,cAAc,CAAA;AAEzD,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,aAAa,EACb,gBAAgB,EAChB,6BAA6B,GAG9B,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAAC,uBAAuB,EAAC,MAAM,mBAAmB,CAAA;AACzD,OAAO,EAAC,qBAAqB,EAAC,MAAM,6BAA6B,CAAA;AAejE,MAAM,OAAO,wBAAyB,SAAQ,KAAK;IAE/B;IADlB,YACkB,IAAyB,EACzC,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAA;QAHE,SAAI,GAAJ,IAAI,CAAqB;QAIzC,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAA;IACxC,CAAC;CACF;AAED,yFAAyF;AACzF,MAAM,mBAAmB;IACf,MAAM,GAAG,KAAK,CAAA;IACd,QAAQ,GAA0B,EAAE,CAAA;IACpC,gBAAgB,GAAG,KAAK,CAAA;IACxB,qBAAqB,GAAG,KAAK,CAAA;IAC7B,aAAa,GAAG,KAAK,CAAA;IACrB,eAAe,GAAyB,IAAI,CAAA;IAEpD,KAAK,CAAC,OAAO,CACX,WAAyC,EACzC,UAAmD;QAEnD,IAAI,IAAI,CAAC,eAAe;YAAE,MAAM,IAAI,CAAC,eAAe,CAAA;QACpD,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAA;QAC9D,CAAC;QACD,IAAI,CAAC,WAAW,CAAC,YAAY,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAA;QAChE,CAAC;QAED,IAAI,KAAK,GAAoB,aAAa,CAAA;QAC1C,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,2BAA2B,EAAE,CAAA;YACxC,UAAU,EAAE,CAAC,EAAC,KAAK,EAAE,aAAa,EAAC,CAAC,CAAA;YACpC,IAAI,CAAC,QAAQ,GAAG,MAAM,gBAAgB,CACpC,aAAa,CAAC,WAAW,CAAC,EAC1B,CAAC,QAAQ,EAAE,EAAE,CAAC,UAAU,EAAE,CAAC,EAAC,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAC,CAAC,EAC5D,QAAQ,CAAC,EAAE,KAAK,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,SAAS,CAC7D,CAAA;YAED,KAAK,GAAG,kBAAkB,CAAA;YAC1B,UAAU,EAAE,CAAC,EAAC,KAAK,EAAE,kBAAkB,EAAC,CAAC,CAAA;YACzC,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAA;YAC5B,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,eAAe,CAAC,IAAI,CAAC,CAAA;YACxD,IAAI,CAAC,sBAAsB,CAAC,OAAO,CAAC,EAAE,CAAC;gBACrC,MAAM,IAAI,wBAAwB,CAAC,sBAAsB,EAAE,gDAAgD,CAAC,CAAA;YAC9G,CAAC;YACD,KAAK,GAAG,iBAAiB,CAAA;YACzB,UAAU,EAAE,CAAC,EAAC,KAAK,EAAE,iBAAiB,EAAC,CAAC,CAAA;YACxC,MAAM,aAAa,GAAG,MAAM,qBAAqB,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAA;YACzF,IAAI,CAAC,qBAAqB,GAAG,IAAI,CAAA;YACjC,IAAI,YAAY,GAAG,aAAa,CAAA;YAChC,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;gBAC1B,IAAI,CAAC;oBACH,YAAY,GAAG,MAAM,eAAe,CAAC,kBAAkB,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;gBAClF,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,MAAM,IAAI,wBAAwB,CAChC,qBAAqB,EACrB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CACvD,CAAA;gBACH,CAAC;YACH,CAAC;YACD,MAAM,aAAa,GAAG,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAA;YAC/G,wFAAwF;YACxF,kFAAkF;YAClF,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,cAAc,CAAC,IAAI,EAAE,aAAa,EAAE,YAAY,CAAC,CAAA;YACtF,IAAI,CAAC,aAAa,GAAG,IAAI,CAAA;YACzB,MAAM,QAAQ,GAAG,6BAA6B,CAAC,WAAW,CAAC,YAAY,EAAE,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,OAAO,CAAC,CAAA;YACvG,MAAM,SAAS,GAAG,MAAM,eAAe,CAAC,cAAc,CAAC,QAAQ,EAAE,aAAa,EAAE,MAAM,CAAC,IAAI,CAAC,CAAA;YAC5F,IAAI,SAAS,CAAC,WAAW,KAAK,MAAM,CAAC,WAAW,EAAE,CAAC;gBACjD,MAAM,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAA;YACpF,CAAC;YACD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAA;YAClB,UAAU,EAAE,CAAC,EAAC,KAAK,EAAE,SAAS,EAAC,CAAC,CAAA;YAChC,OAAO,SAAS,CAAC,WAAW,CAAA;QAC9B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,MAAM,GAAG,IAAI,CAAC,qBAAqB,CAAA;YACzC,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAA;YACrB,IAAI,KAAK,YAAY,gBAAgB,IAAI,KAAK,YAAY,wBAAwB,EAAE,CAAC;gBACnF,MAAM,KAAK,CAAA;YACb,CAAC;YACD,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YACtE,IAAI,KAAK,KAAK,kBAAkB,EAAE,CAAC;gBACjC,MAAM,IAAI,wBAAwB,CAAC,sBAAsB,EAAE,OAAO,CAAC,CAAA;YACrE,CAAC;YACD,IAAI,KAAK,KAAK,iBAAiB,IAAI,CAAC,MAAM,EAAE,CAAC;gBAC3C,MAAM,IAAI,wBAAwB,CAAC,qBAAqB,EAAE,OAAO,CAAC,CAAA;YACpE,CAAC;YACD,MAAM,IAAI,wBAAwB,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAA;QACtE,CAAC;IACH,CAAC;IAED,gGAAgG;IACxF,KAAK,CAAC,2BAA2B;QACvC,IAAI,QAAQ,CAAC,EAAE,KAAK,SAAS;YAAE,OAAM;QAErC,MAAM,UAAU,GAAG,kBAAkB,CAAC,WAAW,CAAC,mBAAmB,CAAA;QACrE,IAAI,MAAM,kBAAkB,CAAC,KAAK,CAAC,UAAU,CAAC;YAAE,OAAM;QACtD,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC,OAAO,CAAC,UAAU,CAAC,CAAA;QAC3D,IAAI,MAAM,KAAK,kBAAkB,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;YAClD,MAAM,IAAI,wBAAwB,CAChC,gCAAgC,EAChC,yEAAyE,CAC1E,CAAA;QACH,CAAC;IACH,CAAC;IAEO,mBAAmB,GAAG,KAAK,EACjC,KAAoB,EACpB,WAAmB,EACnB,UAAkE,EACnC,EAAE;QACjC,MAAM,YAAY,GAAG,eAAe,CAAC,WAAW,CAAC,0BAA0B,EAAE,CAAC,KAAK,EAAE,EAAE;YACrF,IAAI,KAAK,CAAC,WAAW,KAAK,WAAW;gBAAE,UAAU,EAAE,CAAC,KAAK,CAAC,YAAY,EAAE,KAAK,CAAC,aAAa,CAAC,CAAA;QAC9F,CAAC,CAAC,CAAA;QACF,IAAI,CAAC;YACH,OAAO,MAAM,eAAe,CAAC,gBAAgB,CAAC,KAAK,CAAC,GAAG,EAAE,WAAW,CAAC,CAAA;QACvE,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,MAAM,EAAE,CAAA;QACvB,CAAC;IACH,CAAC,CAAA;IAED,KAAK,CAAC,QAAQ;QACZ,IAAI,IAAI,CAAC,eAAe;YAAE,OAAO,IAAI,CAAC,eAAe,CAAA;QACrD,IAAI,CAAC,eAAe,GAAG,CAAC,KAAK,IAAI,EAAE;YACjC,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;gBAC1B,MAAM,cAAc,GAAG,MAAM,uBAAuB,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,eAAe,CAAC,KAAK,CAAC,EAAE;oBAC9F,uEAAuE;oBACvE,wEAAwE;oBACxE,cAAc,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;iBAC7C,CAAC,CAAA;gBACF,IAAI,CAAC,cAAc,EAAE,CAAC;oBACpB,OAAO,CAAC,IAAI,CAAC,iFAAiF,CAAC,CAAA;gBACjG,CAAC;YACH,CAAC;YACD,IAAI,IAAI,CAAC,aAAa;gBAAE,MAAM,eAAe,CAAC,aAAa,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;YAC7E,IAAI,IAAI,CAAC,qBAAqB;gBAAE,MAAM,qBAAqB,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;YACxF,MAAM,gBAAgB,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;YACxC,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAA;YAClB,IAAI,CAAC,MAAM,GAAG,KAAK,CAAA;YACnB,IAAI,CAAC,aAAa,GAAG,KAAK,CAAA;YAC1B,IAAI,CAAC,qBAAqB,GAAG,KAAK,CAAA;YAClC,IAAI,CAAC,gBAAgB,GAAG,KAAK,CAAA;QAC/B,CAAC,CAAC,EAAE,CAAA;QACJ,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,eAAe,CAAA;QAC5B,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,eAAe,GAAG,IAAI,CAAA;QAC7B,CAAC;IACH,CAAC;CACF;AAED,MAAM,CAAC,MAAM,mBAAmB,GAAG,IAAI,mBAAmB,EAAE,CAAA","sourcesContent":["import BluetoothSdk, {isEnabledHotspotStatus, MentraOtaServer} from \"@mentra/bluetooth-sdk/internal\"\n// One coordinator selects the native Android or iOS staging implementation at runtime.\n// eslint-disable-next-line react-native/split-platform-components\nimport {PermissionsAndroid, Platform} from \"react-native\"\nimport type {OtaCheckCurrentGlassesResult} from \"./OtaUpdateCheckService\"\nimport {\n cleanupArtifacts,\n OtaArtifactError,\n planArtifacts,\n prepareArtifacts,\n rewriteManifestForLocalServer,\n type OtaArtifactDownloadProgress,\n type PreparedOtaArtifact,\n} from \"./OtaArtifactDownloader\"\nimport {disableHotspotWithRetry} from \"./HotspotShutdown\"\nimport {localNetworkTransport} from \"./asg/localNetworkTransport\"\n\nexport type HotspotOtaPhase = \"idle\" | \"downloading\" | \"starting_hotspot\" | \"joining_hotspot\" | \"serving\"\n\nexport type HotspotOtaProgress = {\n phase: HotspotOtaPhase\n artifact?: OtaArtifactDownloadProgress\n}\n\nexport type HotspotOtaErrorCode =\n | \"hotspot_wifi_permission_denied\"\n | \"hotspot_start_failed\"\n | \"hotspot_join_failed\"\n | \"hotspot_server_failed\"\n\nexport class HotspotOtaTransportError extends Error {\n constructor(\n public readonly code: HotspotOtaErrorCode,\n message: string,\n ) {\n super(message)\n this.name = \"HotspotOtaTransportError\"\n }\n}\n\n/** Owns the single phone-side endpoint and network lease for one hotspot OTA attempt. */\nclass HotspotOtaTransport {\n private active = false\n private prepared: PreparedOtaArtifact[] = []\n private hotspotRequested = false\n private localNetworkConnected = false\n private serverStarted = false\n private teardownPromise: Promise<void> | null = null\n\n async prepare(\n checkResult: OtaCheckCurrentGlassesResult,\n onProgress?: (progress: HotspotOtaProgress) => void,\n ): Promise<string> {\n if (this.teardownPromise) await this.teardownPromise\n if (this.active) {\n throw new Error(\"A hotspot OTA transport is already active\")\n }\n if (!checkResult.manifestBody) {\n throw new Error(\"The selected OTA check has no manifest body\")\n }\n\n let phase: HotspotOtaPhase = \"downloading\"\n try {\n await this.ensureAndroidWifiPermission()\n onProgress?.({phase: \"downloading\"})\n this.prepared = await prepareArtifacts(\n planArtifacts(checkResult),\n (artifact) => onProgress?.({phase: \"downloading\", artifact}),\n Platform.OS === \"ios\" ? this.downloadIosArtifact : undefined,\n )\n\n phase = \"starting_hotspot\"\n onProgress?.({phase: \"starting_hotspot\"})\n this.hotspotRequested = true\n const hotspot = await BluetoothSdk.setHotspotState(true)\n if (!isEnabledHotspotStatus(hotspot)) {\n throw new HotspotOtaTransportError(\"hotspot_start_failed\", \"Mentra Live did not return hotspot credentials\")\n }\n phase = \"joining_hotspot\"\n onProgress?.({phase: \"joining_hotspot\"})\n const scopedAddress = await localNetworkTransport.connect(hotspot.ssid, hotspot.password)\n this.localNetworkConnected = true\n let localAddress = scopedAddress\n if (Platform.OS === \"ios\") {\n try {\n localAddress = await MentraOtaServer.waitForWifiAddress(hotspot.localIp, 15_000)\n } catch (error) {\n throw new HotspotOtaTransportError(\n \"hotspot_join_failed\",\n error instanceof Error ? error.message : String(error),\n )\n }\n }\n const artifactPaths = Object.fromEntries(this.prepared.map((artifact) => [artifact.sha256, artifact.filePath]))\n // Start exactly one native listener to learn its selected port, then atomically replace\n // the placeholder with the immutable rewritten manifest before ota_start is sent.\n const server = await MentraOtaServer.startOtaServer(\"{}\", artifactPaths, localAddress)\n this.serverStarted = true\n const manifest = rewriteManifestForLocalServer(checkResult.manifestBody, this.prepared, server.baseUrl)\n const published = await MentraOtaServer.startOtaServer(manifest, artifactPaths, server.host)\n if (published.manifestUrl !== server.manifestUrl) {\n throw new Error(\"Local OTA server endpoint changed while publishing the manifest\")\n }\n this.active = true\n onProgress?.({phase: \"serving\"})\n return published.manifestUrl\n } catch (error) {\n const joined = this.localNetworkConnected\n await this.teardown()\n if (error instanceof OtaArtifactError || error instanceof HotspotOtaTransportError) {\n throw error\n }\n const message = error instanceof Error ? error.message : String(error)\n if (phase === \"starting_hotspot\") {\n throw new HotspotOtaTransportError(\"hotspot_start_failed\", message)\n }\n if (phase === \"joining_hotspot\" && !joined) {\n throw new HotspotOtaTransportError(\"hotspot_join_failed\", message)\n }\n throw new HotspotOtaTransportError(\"hotspot_server_failed\", message)\n }\n }\n\n /** Android 13+ requires the nearby-WiFi runtime grant before WifiNetworkSpecifier is usable. */\n private async ensureAndroidWifiPermission(): Promise<void> {\n if (Platform.OS !== \"android\") return\n\n const permission = PermissionsAndroid.PERMISSIONS.NEARBY_WIFI_DEVICES\n if (await PermissionsAndroid.check(permission)) return\n const result = await PermissionsAndroid.request(permission)\n if (result !== PermissionsAndroid.RESULTS.GRANTED) {\n throw new HotspotOtaTransportError(\n \"hotspot_wifi_permission_denied\",\n \"Nearby devices permission is required to connect to the glasses hotspot\",\n )\n }\n }\n\n private downloadIosArtifact = async (\n entry: {url: string},\n destination: string,\n onProgress?: (bytesWritten: number, contentLength: number) => void,\n ): Promise<{statusCode: number}> => {\n const subscription = MentraOtaServer.addListener(\"artifactDownloadProgress\", (event) => {\n if (event.destination === destination) onProgress?.(event.bytesWritten, event.contentLength)\n })\n try {\n return await MentraOtaServer.downloadArtifact(entry.url, destination)\n } finally {\n subscription.remove()\n }\n }\n\n async teardown(): Promise<void> {\n if (this.teardownPromise) return this.teardownPromise\n this.teardownPromise = (async () => {\n if (this.hotspotRequested) {\n const hotspotStopped = await disableHotspotWithRetry(() => BluetoothSdk.setHotspotState(false), {\n // A completed APK step has just replaced ASG. Let the new command path\n // settle before asking it to tear down the SystemUI-owned access point.\n initialDelayMs: this.serverStarted ? 750 : 0,\n })\n if (!hotspotStopped) {\n console.warn(\"[OTA_PROGRESS] glasses hotspot shutdown was not confirmed after bounded retries\")\n }\n }\n if (this.serverStarted) await MentraOtaServer.stopOtaServer().catch(() => {})\n if (this.localNetworkConnected) await localNetworkTransport.disconnect().catch(() => {})\n await cleanupArtifacts().catch(() => {})\n this.prepared = []\n this.active = false\n this.serverStarted = false\n this.localNetworkConnected = false\n this.hotspotRequested = false\n })()\n try {\n await this.teardownPromise\n } finally {\n this.teardownPromise = null\n }\n }\n}\n\nexport const hotspotOtaTransport = new HotspotOtaTransport()\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
type HotspotState = {
|
|
2
|
+
state: "disabled" | "enabled";
|
|
3
|
+
};
|
|
4
|
+
type HotspotShutdownOptions = {
|
|
5
|
+
attempts?: number;
|
|
6
|
+
initialDelayMs?: number;
|
|
7
|
+
retryDelayMs?: number;
|
|
8
|
+
sleep?: (delayMs: number) => Promise<void>;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Disable a glasses hotspot with a bounded retry. ASG APK replacement keeps the
|
|
12
|
+
* BLE link alive through BES, but the first command after the new ASG process
|
|
13
|
+
* appears can still land while its command path is settling.
|
|
14
|
+
*/
|
|
15
|
+
export declare function disableHotspotWithRetry(requestDisabled: () => Promise<HotspotState>, options?: HotspotShutdownOptions): Promise<boolean>;
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=HotspotShutdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HotspotShutdown.d.ts","sourceRoot":"","sources":["../../src/services/HotspotShutdown.ts"],"names":[],"mappings":"AAAA,KAAK,YAAY,GAAG;IAAC,KAAK,EAAE,UAAU,GAAG,SAAS,CAAA;CAAC,CAAA;AAEnD,KAAK,sBAAsB,GAAG;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3C,CAAA;AAID;;;;GAIG;AACH,wBAAsB,uBAAuB,CAC3C,eAAe,EAAE,MAAM,OAAO,CAAC,YAAY,CAAC,EAC5C,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,OAAO,CAAC,CAqBlB"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const sleep = (delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
2
|
+
/**
|
|
3
|
+
* Disable a glasses hotspot with a bounded retry. ASG APK replacement keeps the
|
|
4
|
+
* BLE link alive through BES, but the first command after the new ASG process
|
|
5
|
+
* appears can still land while its command path is settling.
|
|
6
|
+
*/
|
|
7
|
+
export async function disableHotspotWithRetry(requestDisabled, options = {}) {
|
|
8
|
+
const attempts = Math.max(1, options.attempts ?? 2);
|
|
9
|
+
const initialDelayMs = Math.max(0, options.initialDelayMs ?? 0);
|
|
10
|
+
const retryDelayMs = Math.max(0, options.retryDelayMs ?? 500);
|
|
11
|
+
const wait = options.sleep ?? sleep;
|
|
12
|
+
if (initialDelayMs > 0)
|
|
13
|
+
await wait(initialDelayMs);
|
|
14
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
15
|
+
try {
|
|
16
|
+
const status = await requestDisabled();
|
|
17
|
+
if (status.state === "disabled")
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// A lost command is indistinguishable from a response timeout here. Retry
|
|
22
|
+
// while the phone still owns the hotspot network and local server.
|
|
23
|
+
}
|
|
24
|
+
if (attempt < attempts && retryDelayMs > 0)
|
|
25
|
+
await wait(retryDelayMs);
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=HotspotShutdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HotspotShutdown.js","sourceRoot":"","sources":["../../src/services/HotspotShutdown.ts"],"names":[],"mappings":"AASA,MAAM,KAAK,GAAG,CAAC,OAAe,EAAE,EAAE,CAAC,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAA;AAE/F;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,eAA4C,EAC5C,UAAkC,EAAE;IAEpC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAA;IACnD,MAAM,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,cAAc,IAAI,CAAC,CAAC,CAAA;IAC/D,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,YAAY,IAAI,GAAG,CAAC,CAAA;IAC7D,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,IAAI,KAAK,CAAA;IAEnC,IAAI,cAAc,GAAG,CAAC;QAAE,MAAM,IAAI,CAAC,cAAc,CAAC,CAAA;IAElD,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,QAAQ,EAAE,OAAO,IAAI,CAAC,EAAE,CAAC;QACxD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,eAAe,EAAE,CAAA;YACtC,IAAI,MAAM,CAAC,KAAK,KAAK,UAAU;gBAAE,OAAO,IAAI,CAAA;QAC9C,CAAC;QAAC,MAAM,CAAC;YACP,0EAA0E;YAC1E,mEAAmE;QACrE,CAAC;QAED,IAAI,OAAO,GAAG,QAAQ,IAAI,YAAY,GAAG,CAAC;YAAE,MAAM,IAAI,CAAC,YAAY,CAAC,CAAA;IACtE,CAAC;IAED,OAAO,KAAK,CAAA;AACd,CAAC","sourcesContent":["type HotspotState = {state: \"disabled\" | \"enabled\"}\n\ntype HotspotShutdownOptions = {\n attempts?: number\n initialDelayMs?: number\n retryDelayMs?: number\n sleep?: (delayMs: number) => Promise<void>\n}\n\nconst sleep = (delayMs: number) => new Promise<void>((resolve) => setTimeout(resolve, delayMs))\n\n/**\n * Disable a glasses hotspot with a bounded retry. ASG APK replacement keeps the\n * BLE link alive through BES, but the first command after the new ASG process\n * appears can still land while its command path is settling.\n */\nexport async function disableHotspotWithRetry(\n requestDisabled: () => Promise<HotspotState>,\n options: HotspotShutdownOptions = {},\n): Promise<boolean> {\n const attempts = Math.max(1, options.attempts ?? 2)\n const initialDelayMs = Math.max(0, options.initialDelayMs ?? 0)\n const retryDelayMs = Math.max(0, options.retryDelayMs ?? 500)\n const wait = options.sleep ?? sleep\n\n if (initialDelayMs > 0) await wait(initialDelayMs)\n\n for (let attempt = 1; attempt <= attempts; attempt += 1) {\n try {\n const status = await requestDisabled()\n if (status.state === \"disabled\") return true\n } catch {\n // A lost command is indistinguishable from a response timeout here. Retry\n // while the phone still owns the hotspot network and local server.\n }\n\n if (attempt < attempts && retryDelayMs > 0) await wait(retryDelayMs)\n }\n\n return false\n}\n"]}
|
|
@@ -74,6 +74,8 @@ declare class LocalDisplayManager {
|
|
|
74
74
|
* miniapp is foreground.
|
|
75
75
|
*/
|
|
76
76
|
onCoreAppChange(packageName: string | null): void;
|
|
77
|
+
/** Report-safe ownership snapshot for incident diagnostics. */
|
|
78
|
+
getDiagnosticSnapshot(): Record<string, unknown>;
|
|
77
79
|
/**
|
|
78
80
|
* A local miniapp is going away. Clean up all state associated with it.
|
|
79
81
|
*/
|
|
@@ -88,7 +90,20 @@ declare class LocalDisplayManager {
|
|
|
88
90
|
* the boot queue (resolved when drained or superseded).
|
|
89
91
|
*/
|
|
90
92
|
request(packageName: string, payload: DisplayPayload, resolve?: DisplayRequestResolver): void;
|
|
93
|
+
/**
|
|
94
|
+
* End a package's temporary display early. If it currently owns the main
|
|
95
|
+
* view, release its background lock and restore the foreground miniapp's
|
|
96
|
+
* saved frame. A dismissal for a package that is no longer visible is a
|
|
97
|
+
* no-op.
|
|
98
|
+
*/
|
|
99
|
+
dismiss(packageName: string): void;
|
|
91
100
|
private arbitrateAndSend;
|
|
101
|
+
/**
|
|
102
|
+
* An app cleared its display ("render nothing"). Same semantics as its
|
|
103
|
+
* frame expiring: give up ownership, restore the core app's still-valid
|
|
104
|
+
* display if there is one, otherwise clear the glasses.
|
|
105
|
+
*/
|
|
106
|
+
private handleAppClear;
|
|
92
107
|
private sendNow;
|
|
93
108
|
/**
|
|
94
109
|
* Send a scene through SceneRenderer. On positioning devices this emits a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocalDisplayManager.d.ts","sourceRoot":"","sources":["../../src/services/LocalDisplayManager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAQH,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,wBAAwB,CAAA;AAO7D,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,MAAM,CAAA;IACpC,kFAAkF;IAClF,MAAM,CAAC,EAAE;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAC,CAAA;IACrD,qEAAqE;IACrE,KAAK,CAAC,EAAE,iBAAiB,EAAE,CAAA;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,+EAA+E;AAC/E,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,WAAW,GAAG,SAAS,CAAA;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,MAAM,sBAAsB,GAAG,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"LocalDisplayManager.d.ts","sourceRoot":"","sources":["../../src/services/LocalDisplayManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -44,6 +44,12 @@ const BOOT_DURATION_MS = 1500;
|
|
|
44
44
|
const BACKGROUND_LOCK_TIMEOUT_MS = 10_000;
|
|
45
45
|
// Sentinel package name used as the "source" of the system boot message.
|
|
46
46
|
const SYSTEM_BOOT_PKG = "system.boot";
|
|
47
|
+
/** "Render nothing" — an empty scene or an explicit clear_view layout. */
|
|
48
|
+
function isClearPayload(payload) {
|
|
49
|
+
if (payload.scene)
|
|
50
|
+
return payload.scene.length === 0;
|
|
51
|
+
return payload.layout?.layoutType === "clear_view";
|
|
52
|
+
}
|
|
47
53
|
// =============================================================================
|
|
48
54
|
// LocalDisplayManager
|
|
49
55
|
// =============================================================================
|
|
@@ -79,13 +85,19 @@ class LocalDisplayManager {
|
|
|
79
85
|
*/
|
|
80
86
|
onMount(packageName, displayName) {
|
|
81
87
|
console.log(`${LOG_TAG}: onMount(${packageName}, "${displayName}")`);
|
|
82
|
-
// Cancel any in-flight boot for a prior app.
|
|
83
|
-
this.cancelBoot();
|
|
84
88
|
// Snapshot whatever's on the glasses BEFORE the boot text overwrites it.
|
|
85
89
|
// registerApp fires onMount for every spawn — including a background app or
|
|
86
90
|
// a crash-respawn while another app is foreground — so a boot that times out
|
|
87
91
|
// without rendering must put the prior frame back rather than blank it.
|
|
88
|
-
|
|
92
|
+
// Never snapshot the boot text itself: back-to-back mounts (the autostart
|
|
93
|
+
// restore loop, crash-respawn chains) land while the previous app's
|
|
94
|
+
// "Starting X…" is still on the glasses, and endBoot would "restore" that
|
|
95
|
+
// stale, expiry-less frame permanently. Across chained boots, inherit the
|
|
96
|
+
// original pre-boot frame instead.
|
|
97
|
+
const inherited = this.bootingApp ? this.bootingApp.preBoot : this.currentDisplay;
|
|
98
|
+
const preBoot = inherited?.packageName === SYSTEM_BOOT_PKG ? null : inherited;
|
|
99
|
+
// Cancel any in-flight boot for a prior app.
|
|
100
|
+
this.cancelBoot();
|
|
89
101
|
// Send the boot message directly (bypass arbitration — this is a system
|
|
90
102
|
// display). Goes through sendNow so scene devices render it as a scene:
|
|
91
103
|
// otherwise every app start would be a legacy→scene transition on the
|
|
@@ -95,7 +107,7 @@ class LocalDisplayManager {
|
|
|
95
107
|
layout: { layoutType: "text_wall", text: `Starting ${displayName}…` },
|
|
96
108
|
});
|
|
97
109
|
const timerId = BgTimer.setTimeout(() => {
|
|
98
|
-
this.endBoot(
|
|
110
|
+
this.endBoot();
|
|
99
111
|
}, BOOT_DURATION_MS);
|
|
100
112
|
this.bootingApp = {
|
|
101
113
|
packageName,
|
|
@@ -123,14 +135,38 @@ class LocalDisplayManager {
|
|
|
123
135
|
this.coreAppDisplay = null;
|
|
124
136
|
}
|
|
125
137
|
}
|
|
138
|
+
/** Report-safe ownership snapshot for incident diagnostics. */
|
|
139
|
+
getDiagnosticSnapshot() {
|
|
140
|
+
return {
|
|
141
|
+
coreAppPackageName: this.coreApp,
|
|
142
|
+
currentDisplayPackageName: this.currentDisplay?.packageName ?? null,
|
|
143
|
+
backgroundLockPackageName: this.backgroundLock?.packageName ?? null,
|
|
144
|
+
bootingPackageName: this.bootingApp?.packageName ?? null,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
126
147
|
/**
|
|
127
148
|
* A local miniapp is going away. Clean up all state associated with it.
|
|
128
149
|
*/
|
|
129
150
|
onUnmount(packageName) {
|
|
130
151
|
console.log(`${LOG_TAG}: onUnmount(${packageName})`);
|
|
131
|
-
// If this was the booting app, cancel boot
|
|
152
|
+
// If this was the booting app, cancel boot — and don't strand its
|
|
153
|
+
// "Starting X…" text (a system frame with no expiry): put back what the
|
|
154
|
+
// boot covered, or clear.
|
|
132
155
|
if (this.bootingApp?.packageName === packageName) {
|
|
156
|
+
const preBoot = this.bootingApp.preBoot;
|
|
133
157
|
this.cancelBoot();
|
|
158
|
+
if (this.currentDisplay?.packageName === SYSTEM_BOOT_PKG) {
|
|
159
|
+
// The preBoot owner may have forfeited its frame during the boot
|
|
160
|
+
// window (a clear parked in the boot queue) — don't resurrect it.
|
|
161
|
+
const ownerQueued = preBoot ? this.bootQueue.get(preBoot.packageName) : undefined;
|
|
162
|
+
const preBootForfeited = ownerQueued !== undefined && isClearPayload(ownerQueued.payload);
|
|
163
|
+
if (preBoot && !preBootForfeited && (preBoot.expiresAt === null || preBoot.expiresAt > this.now())) {
|
|
164
|
+
this.restoreDisplay(preBoot);
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
this.sendClear();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
134
170
|
}
|
|
135
171
|
this.bootQueue.get(packageName)?.resolve?.({ status: "blocked", reason: "app stopped" });
|
|
136
172
|
this.bootQueue.delete(packageName);
|
|
@@ -140,10 +176,17 @@ class LocalDisplayManager {
|
|
|
140
176
|
if (this.backgroundLock?.packageName === packageName) {
|
|
141
177
|
this.backgroundLock = null;
|
|
142
178
|
}
|
|
143
|
-
//
|
|
179
|
+
// Drop the saved core frame if the core's context is going away — the
|
|
180
|
+
// frame belongs to a dead context and must not be restored later. The
|
|
181
|
+
// coreApp POINTER itself is NOT cleared here: it is derived state owned
|
|
182
|
+
// by the apps-store reconciler. A liveness/crash respawn tears the
|
|
183
|
+
// context down (this path) without any store change — the app is still
|
|
184
|
+
// "running" — and clearing the pointer here would leave the respawned
|
|
185
|
+
// foreground app arbitrating as background until an unrelated store
|
|
186
|
+
// write happened to re-run the reconciler. Real stops release the slot
|
|
187
|
+
// via the store (running → false → reconciler → onCoreAppChange(null)).
|
|
144
188
|
if (this.coreApp === packageName) {
|
|
145
189
|
this.coreAppDisplay = null;
|
|
146
|
-
this.coreApp = null;
|
|
147
190
|
}
|
|
148
191
|
// If this app owned the current on-glasses display, clear it (and maybe
|
|
149
192
|
// restore the core app's display if it still has time left).
|
|
@@ -183,12 +226,21 @@ class LocalDisplayManager {
|
|
|
183
226
|
superseded?.resolve?.({ status: "blocked", reason: "superseded by a newer frame" });
|
|
184
227
|
this.bootQueue.set(packageName, { payload, resolve });
|
|
185
228
|
if (this.bootingApp.packageName === packageName) {
|
|
186
|
-
this.endBoot(
|
|
229
|
+
this.endBoot();
|
|
187
230
|
}
|
|
188
231
|
return;
|
|
189
232
|
}
|
|
190
233
|
this.arbitrateAndSend(packageName, payload, resolve);
|
|
191
234
|
}
|
|
235
|
+
/**
|
|
236
|
+
* End a package's temporary display early. If it currently owns the main
|
|
237
|
+
* view, release its background lock and restore the foreground miniapp's
|
|
238
|
+
* saved frame. A dismissal for a package that is no longer visible is a
|
|
239
|
+
* no-op.
|
|
240
|
+
*/
|
|
241
|
+
dismiss(packageName) {
|
|
242
|
+
this.handleExpiry(packageName);
|
|
243
|
+
}
|
|
192
244
|
// ===========================================================================
|
|
193
245
|
// Internals — arbitration
|
|
194
246
|
// ===========================================================================
|
|
@@ -198,6 +250,15 @@ class LocalDisplayManager {
|
|
|
198
250
|
if (this.backgroundLock && now > this.backgroundLock.expiresAt) {
|
|
199
251
|
this.backgroundLock = null;
|
|
200
252
|
}
|
|
253
|
+
// A clear ("render nothing") means "I'm done with the display" — it is a
|
|
254
|
+
// forfeit, not a frame. A background app releases its lock and the core
|
|
255
|
+
// app's still-valid display comes back; treating it as a render instead
|
|
256
|
+
// would RENEW the lock with an empty frame and blank the glasses while
|
|
257
|
+
// still blocking everyone else.
|
|
258
|
+
if (isClearPayload(payload)) {
|
|
259
|
+
this.handleAppClear(packageName, resolve);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
201
262
|
const isCore = packageName === this.coreApp;
|
|
202
263
|
if (isCore) {
|
|
203
264
|
// Save so we can restore later.
|
|
@@ -246,6 +307,30 @@ class LocalDisplayManager {
|
|
|
246
307
|
}
|
|
247
308
|
this.sendNow(packageName, payload, resolve);
|
|
248
309
|
}
|
|
310
|
+
/**
|
|
311
|
+
* An app cleared its display ("render nothing"). Same semantics as its
|
|
312
|
+
* frame expiring: give up ownership, restore the core app's still-valid
|
|
313
|
+
* display if there is one, otherwise clear the glasses.
|
|
314
|
+
*/
|
|
315
|
+
handleAppClear(packageName, resolve) {
|
|
316
|
+
// Forget the app's retained scene state — its next render starts fresh.
|
|
317
|
+
sceneRenderer.clearApp(packageName);
|
|
318
|
+
if (this.backgroundLock?.packageName === packageName) {
|
|
319
|
+
this.backgroundLock = null;
|
|
320
|
+
}
|
|
321
|
+
if (this.coreApp === packageName) {
|
|
322
|
+
this.coreAppDisplay = null;
|
|
323
|
+
}
|
|
324
|
+
if (this.currentDisplay?.packageName === packageName) {
|
|
325
|
+
this.clearExpiryTimer();
|
|
326
|
+
this.currentDisplay = null;
|
|
327
|
+
this.tryRestoreCoreDisplay();
|
|
328
|
+
if (!this.currentDisplay) {
|
|
329
|
+
this.sendClear();
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
resolve?.({ status: "displayed" });
|
|
333
|
+
}
|
|
249
334
|
// ===========================================================================
|
|
250
335
|
// Internals — send
|
|
251
336
|
// ===========================================================================
|
|
@@ -411,7 +496,7 @@ class LocalDisplayManager {
|
|
|
411
496
|
BgTimer.clearTimeout(this.bootingApp.timerId);
|
|
412
497
|
this.bootingApp = null;
|
|
413
498
|
}
|
|
414
|
-
endBoot(
|
|
499
|
+
endBoot() {
|
|
415
500
|
if (!this.bootingApp)
|
|
416
501
|
return;
|
|
417
502
|
const preBoot = this.bootingApp.preBoot;
|
|
@@ -430,17 +515,18 @@ class LocalDisplayManager {
|
|
|
430
515
|
continue;
|
|
431
516
|
this.arbitrateAndSend(pkg, entry.payload, entry.resolve);
|
|
432
517
|
}
|
|
433
|
-
//
|
|
434
|
-
//
|
|
435
|
-
//
|
|
436
|
-
//
|
|
437
|
-
//
|
|
438
|
-
//
|
|
439
|
-
//
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
518
|
+
// If the boot text is still on the glasses — nothing was queued, or every
|
|
519
|
+
// drained request lost arbitration — it must not strand: it's a system
|
|
520
|
+
// frame with no expiry. Restore whatever was on the glasses before the
|
|
521
|
+
// boot, else clear. Two reasons NOT to restore: the frame's duration
|
|
522
|
+
// elapsed during the boot window, or its owner FORFEITED it during the
|
|
523
|
+
// window (its final queued intent — the queue keeps last-wins per app —
|
|
524
|
+
// was a clear, which the drain above processed as a forfeit; restoring
|
|
525
|
+
// would resurrect content the clear removed).
|
|
526
|
+
if (this.currentDisplay?.packageName === SYSTEM_BOOT_PKG) {
|
|
527
|
+
const ownerEntry = preBoot ? queued.find(([pkg]) => pkg === preBoot.packageName) : undefined;
|
|
528
|
+
const preBootForfeited = ownerEntry !== undefined && isClearPayload(ownerEntry[1].payload);
|
|
529
|
+
if (preBoot && !preBootForfeited && (preBoot.expiresAt === null || preBoot.expiresAt > this.now())) {
|
|
444
530
|
this.restoreDisplay(preBoot);
|
|
445
531
|
}
|
|
446
532
|
else {
|