@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,53 @@
|
|
|
1
|
+
export interface WifiAdbRequestDependencies {
|
|
2
|
+
isSystemPackage: (packageName: string) => boolean
|
|
3
|
+
setWifiAdbState: (enabled: boolean) => Promise<void>
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export type WifiAdbRequestErrorCode = "NOT_PERMITTED" | "INVALID_ARGUMENT" | "INTERNAL"
|
|
7
|
+
|
|
8
|
+
export type WifiAdbRequestResult =
|
|
9
|
+
| {ok: true}
|
|
10
|
+
| {
|
|
11
|
+
ok: false
|
|
12
|
+
error: {code: WifiAdbRequestErrorCode; message: string}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Apply the SYSTEM-only Wi-Fi ADB policy before calling the native SDK. */
|
|
16
|
+
export async function handleWifiAdbRequest(
|
|
17
|
+
packageName: string,
|
|
18
|
+
payload: Record<string, unknown>,
|
|
19
|
+
dependencies: WifiAdbRequestDependencies,
|
|
20
|
+
): Promise<WifiAdbRequestResult> {
|
|
21
|
+
if (!dependencies.isSystemPackage(packageName)) {
|
|
22
|
+
return {
|
|
23
|
+
ok: false,
|
|
24
|
+
error: {
|
|
25
|
+
code: "NOT_PERMITTED",
|
|
26
|
+
message: "setWifiAdbState is restricted to system apps",
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (typeof payload.enabled !== "boolean") {
|
|
32
|
+
return {
|
|
33
|
+
ok: false,
|
|
34
|
+
error: {
|
|
35
|
+
code: "INVALID_ARGUMENT",
|
|
36
|
+
message: "enabled must be a boolean",
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
await dependencies.setWifiAdbState(payload.enabled)
|
|
43
|
+
return {ok: true}
|
|
44
|
+
} catch (error) {
|
|
45
|
+
return {
|
|
46
|
+
ok: false,
|
|
47
|
+
error: {
|
|
48
|
+
code: "INTERNAL",
|
|
49
|
+
message: error instanceof Error ? error.message : "Wi-Fi ADB error",
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -1071,7 +1071,13 @@ export class AsgCameraApiClient {
|
|
|
1071
1071
|
capture: CaptureGroup,
|
|
1072
1072
|
onProgress?: (bytesDownloaded: number, totalBytes: number) => void,
|
|
1073
1073
|
abortSignal?: AbortSignal,
|
|
1074
|
-
): Promise<{
|
|
1074
|
+
): Promise<{
|
|
1075
|
+
captureDir: string
|
|
1076
|
+
primaryPath: string
|
|
1077
|
+
bracketPaths: string[]
|
|
1078
|
+
sidecarPath?: string
|
|
1079
|
+
thumbnailPath?: string
|
|
1080
|
+
}> {
|
|
1075
1081
|
if (capture.files.some((file) => !file.size || file.size <= 0)) {
|
|
1076
1082
|
// Preserve support for malformed manifests from pre-v2 development firmware.
|
|
1077
1083
|
return this.downloadCaptureLegacyUnsafe(capture, onProgress, abortSignal)
|
|
@@ -1136,9 +1142,14 @@ export class AsgCameraApiClient {
|
|
|
1136
1142
|
const leafName = sortedFiles[0].name.includes("/") ? sortedFiles[0].name.split("/").pop()! : sortedFiles[0].name
|
|
1137
1143
|
primaryPath = `${captureDir}/${leafName}`
|
|
1138
1144
|
}
|
|
1145
|
+
const primaryFile = sortedFiles.find((file) => file.role === "primary") || sortedFiles[0]
|
|
1146
|
+
const thumbnailPath =
|
|
1147
|
+
capture.type === "video" && primaryFile
|
|
1148
|
+
? await this.downloadVideoThumbnail(primaryFile.name, `${captureDir}/.thumb.jpg`, abortSignal)
|
|
1149
|
+
: undefined
|
|
1139
1150
|
galleryTransferLedger.transition(capture.capture_id, "VERIFIED")
|
|
1140
1151
|
onProgress?.(capture.total_size, capture.total_size)
|
|
1141
|
-
return {captureDir, primaryPath, bracketPaths, sidecarPath}
|
|
1152
|
+
return {captureDir, primaryPath, bracketPaths, sidecarPath, thumbnailPath}
|
|
1142
1153
|
} catch (error) {
|
|
1143
1154
|
galleryTransferLedger.recordFailure(capture.capture_id, error)
|
|
1144
1155
|
throw error
|
|
@@ -1150,7 +1161,13 @@ export class AsgCameraApiClient {
|
|
|
1150
1161
|
capture: CaptureGroup,
|
|
1151
1162
|
onProgress?: (bytesDownloaded: number, totalBytes: number) => void,
|
|
1152
1163
|
abortSignal?: AbortSignal,
|
|
1153
|
-
): Promise<{
|
|
1164
|
+
): Promise<{
|
|
1165
|
+
captureDir: string
|
|
1166
|
+
primaryPath: string
|
|
1167
|
+
bracketPaths: string[]
|
|
1168
|
+
sidecarPath?: string
|
|
1169
|
+
thumbnailPath?: string
|
|
1170
|
+
}> {
|
|
1154
1171
|
const captureDir = localStorageService.getPhotoFilePath(capture.capture_id)
|
|
1155
1172
|
console.log(
|
|
1156
1173
|
`[ASG Camera API] downloadCapture: ${capture.capture_id} (${capture.files.length} files) -> ${captureDir}`,
|
|
@@ -1300,7 +1317,76 @@ export class AsgCameraApiClient {
|
|
|
1300
1317
|
onProgress(totalBytes, totalBytes)
|
|
1301
1318
|
}
|
|
1302
1319
|
|
|
1303
|
-
|
|
1320
|
+
const primaryFile = sortedFiles.find((file) => file.role === "primary") || sortedFiles[0]
|
|
1321
|
+
const thumbnailPath =
|
|
1322
|
+
capture.type === "video" && primaryFile
|
|
1323
|
+
? await this.downloadVideoThumbnail(primaryFile.name, `${captureDir}/.thumb.jpg`, abortSignal)
|
|
1324
|
+
: undefined
|
|
1325
|
+
|
|
1326
|
+
return {captureDir, primaryPath, bracketPaths, sidecarPath, thumbnailPath}
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
/** Download an on-demand video thumbnail while the source capture is still on the glasses. */
|
|
1330
|
+
private async downloadVideoThumbnail(
|
|
1331
|
+
sourceFileName: string,
|
|
1332
|
+
thumbnailPath: string,
|
|
1333
|
+
abortSignal?: AbortSignal,
|
|
1334
|
+
): Promise<string | undefined> {
|
|
1335
|
+
const partialPath = `${thumbnailPath}.partial`
|
|
1336
|
+
const parentDir = thumbnailPath.substring(0, thumbnailPath.lastIndexOf("/"))
|
|
1337
|
+
|
|
1338
|
+
try {
|
|
1339
|
+
if (parentDir && !(await RNFS.exists(parentDir))) {
|
|
1340
|
+
await RNFS.mkdir(parentDir)
|
|
1341
|
+
}
|
|
1342
|
+
await RNFS.unlink(partialPath).catch(() => {})
|
|
1343
|
+
const {jobId, promise} = localNetworkTransport.downloadFile({
|
|
1344
|
+
fromUrl: `${this.baseUrl}/api/photo?file=${encodeURIComponent(sourceFileName)}`,
|
|
1345
|
+
toFile: partialPath,
|
|
1346
|
+
headers: {
|
|
1347
|
+
"Accept": "image/jpeg",
|
|
1348
|
+
"User-Agent": "MentraOS-Mobile/1.0",
|
|
1349
|
+
},
|
|
1350
|
+
connectionTimeout: 60000,
|
|
1351
|
+
readTimeout: 60000,
|
|
1352
|
+
progressDivider: 10,
|
|
1353
|
+
})
|
|
1354
|
+
|
|
1355
|
+
let abortPollTimer: number | undefined
|
|
1356
|
+
if (abortSignal) {
|
|
1357
|
+
if (abortSignal.aborted) {
|
|
1358
|
+
localNetworkTransport.stopDownload(jobId)
|
|
1359
|
+
throw new Error("Sync cancelled")
|
|
1360
|
+
}
|
|
1361
|
+
abortPollTimer = BgTimer.setInterval(() => {
|
|
1362
|
+
if (abortSignal.aborted) localNetworkTransport.stopDownload(jobId)
|
|
1363
|
+
}, 500)
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
let result
|
|
1367
|
+
try {
|
|
1368
|
+
result = await promise
|
|
1369
|
+
} finally {
|
|
1370
|
+
if (abortPollTimer !== undefined) BgTimer.clearInterval(abortPollTimer)
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
if (abortSignal?.aborted) throw new Error("Sync cancelled")
|
|
1374
|
+
if (result.statusCode !== 200 || result.bytesWritten <= 0) {
|
|
1375
|
+
throw new Error(`HTTP ${result.statusCode}, ${result.bytesWritten} bytes`)
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
await RNFS.unlink(thumbnailPath).catch(() => {})
|
|
1379
|
+
await RNFS.moveFile(partialPath, thumbnailPath)
|
|
1380
|
+
console.log(`[ASG Camera API] Downloaded video thumbnail for ${sourceFileName}`)
|
|
1381
|
+
return thumbnailPath
|
|
1382
|
+
} catch (error) {
|
|
1383
|
+
await RNFS.unlink(partialPath).catch(() => {})
|
|
1384
|
+
if (abortSignal?.aborted || (error instanceof Error && error.message === "Sync cancelled")) {
|
|
1385
|
+
throw new Error("Sync cancelled")
|
|
1386
|
+
}
|
|
1387
|
+
console.warn(`[ASG Camera API] Failed to download video thumbnail for ${sourceFileName}:`, error)
|
|
1388
|
+
return undefined
|
|
1389
|
+
}
|
|
1304
1390
|
}
|
|
1305
1391
|
|
|
1306
1392
|
/**
|
|
@@ -1421,15 +1507,20 @@ export class AsgCameraApiClient {
|
|
|
1421
1507
|
mediaKind: isVideo ? "video" : "photo",
|
|
1422
1508
|
})
|
|
1423
1509
|
galleryTransferLedger.transition(captureId, "VERIFIED")
|
|
1510
|
+
const thumbnailPath =
|
|
1511
|
+
includeThumbnail && isVideo
|
|
1512
|
+
? await this.downloadVideoThumbnail(filename, localStorageService.getThumbnailFilePath(filename), abortSignal)
|
|
1513
|
+
: undefined
|
|
1424
1514
|
return {
|
|
1425
1515
|
filePath: localFilePath,
|
|
1516
|
+
thumbnailPath,
|
|
1426
1517
|
mime_type: isVideo
|
|
1427
1518
|
? "video/mp4"
|
|
1428
1519
|
: lowerName.endsWith(".png")
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1520
|
+
? "image/png"
|
|
1521
|
+
: lowerName.endsWith(".avif")
|
|
1522
|
+
? "image/avif"
|
|
1523
|
+
: "image/jpeg",
|
|
1433
1524
|
}
|
|
1434
1525
|
} catch (error) {
|
|
1435
1526
|
galleryTransferLedger.recordFailure(captureId, error)
|
|
@@ -156,6 +156,10 @@ class GallerySyncService {
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
this.syncStartPromise = null
|
|
159
|
+
useGallerySyncStore.getState().setSyncStarting(false)
|
|
160
|
+
this.waitingForWifiRetry = false
|
|
161
|
+
this.wifiSettingsOpenedAt = null
|
|
162
|
+
this.startAborted = false
|
|
159
163
|
this.isInitialized = false
|
|
160
164
|
console.log("[GallerySyncService] Cleaned up")
|
|
161
165
|
}
|
|
@@ -228,14 +232,23 @@ class GallerySyncService {
|
|
|
228
232
|
if (!glassesConnected) {
|
|
229
233
|
console.log("[GallerySyncService] Glasses disconnected - not retrying sync")
|
|
230
234
|
this.waitingForWifiRetry = false
|
|
235
|
+
this.wifiSettingsOpenedAt = null
|
|
231
236
|
return
|
|
232
237
|
}
|
|
233
238
|
|
|
239
|
+
// The user has returned from the WiFi settings prompt, so the previous
|
|
240
|
+
// "Sync failed" state is no longer actionable. Make the button available
|
|
241
|
+
// immediately while the native WiFi state catches up; a successful check
|
|
242
|
+
// below will start the sync automatically.
|
|
243
|
+
if (store.syncState === "error" && store.lastError?.startsWith("WiFi disabled")) {
|
|
244
|
+
store.setSyncState("idle")
|
|
245
|
+
}
|
|
246
|
+
|
|
234
247
|
// Check if WiFi is now enabled (Android only)
|
|
235
248
|
// Use retry logic because WiFi status takes time to propagate after user enables it
|
|
236
249
|
if (Platform.OS === "android") {
|
|
237
250
|
const MAX_RETRIES = 5
|
|
238
|
-
const RETRY_DELAY_MS = 1000
|
|
251
|
+
const RETRY_DELAY_MS = 1000
|
|
239
252
|
|
|
240
253
|
console.log("[GallerySyncService] Waiting for WiFi to initialize (may take a moment after enabling)...")
|
|
241
254
|
|
|
@@ -243,35 +256,28 @@ class GallerySyncService {
|
|
|
243
256
|
try {
|
|
244
257
|
await new Promise<void>((resolve) => BgTimer.setTimeout(() => resolve(), RETRY_DELAY_MS))
|
|
245
258
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
)
|
|
259
|
+
// NetInfo can remain stale after Android's WiFi settings close. Use
|
|
260
|
+
// the same native source as the normal sync preflight.
|
|
261
|
+
const wifiEnabled = await WifiManager.isEnabled()
|
|
262
|
+
console.log(`[GallerySyncService] WiFi check attempt ${attempt}/${MAX_RETRIES}: enabled=${wifiEnabled}`)
|
|
250
263
|
|
|
251
|
-
if (
|
|
264
|
+
if (wifiEnabled) {
|
|
252
265
|
console.log("[GallerySyncService] ✅ WiFi is now enabled - auto-retrying sync")
|
|
253
266
|
this.waitingForWifiRetry = false
|
|
254
267
|
this.wifiSettingsOpenedAt = null // Clear cooldown timestamp
|
|
255
|
-
// Clear previous error state
|
|
256
|
-
store.setSyncState("idle")
|
|
257
268
|
// Auto-retry sync
|
|
258
269
|
await this.startSync()
|
|
259
270
|
return
|
|
260
271
|
}
|
|
261
|
-
|
|
262
|
-
// If this was the last attempt, log and give up
|
|
263
|
-
if (attempt === MAX_RETRIES) {
|
|
264
|
-
console.log(
|
|
265
|
-
"[GallerySyncService] ❌ WiFi still disabled after all retries - user may need to tap sync manually",
|
|
266
|
-
)
|
|
267
|
-
this.waitingForWifiRetry = false
|
|
268
|
-
this.wifiSettingsOpenedAt = null // Clear cooldown timestamp
|
|
269
|
-
}
|
|
270
272
|
} catch (error) {
|
|
271
273
|
console.warn(`[GallerySyncService] Failed to check WiFi status on attempt ${attempt}:`, error)
|
|
272
274
|
// Continue to next retry
|
|
273
275
|
}
|
|
274
276
|
}
|
|
277
|
+
|
|
278
|
+
console.log("[GallerySyncService] WiFi not detected after retries - leaving sync available for manual retry")
|
|
279
|
+
this.waitingForWifiRetry = false
|
|
280
|
+
this.wifiSettingsOpenedAt = null
|
|
275
281
|
}
|
|
276
282
|
}
|
|
277
283
|
|
|
@@ -378,8 +384,10 @@ class GallerySyncService {
|
|
|
378
384
|
return this.syncStartPromise
|
|
379
385
|
}
|
|
380
386
|
|
|
387
|
+
useGallerySyncStore.getState().setSyncStarting(true)
|
|
381
388
|
this.syncStartPromise = this.runStartSync().finally(() => {
|
|
382
389
|
this.syncStartPromise = null
|
|
390
|
+
useGallerySyncStore.getState().setSyncStarting(false)
|
|
383
391
|
})
|
|
384
392
|
return this.syncStartPromise
|
|
385
393
|
}
|
|
@@ -1321,7 +1329,7 @@ class GallerySyncService {
|
|
|
1321
1329
|
}
|
|
1322
1330
|
}
|
|
1323
1331
|
// Old firmware remains on the exact v2 endpoint/schema. Avoid expensive inline thumbnails;
|
|
1324
|
-
//
|
|
1332
|
+
// video thumbnails are fetched as small side downloads with their primary media.
|
|
1325
1333
|
const syncResponse = await asgCameraApi.syncWithServer(clientId, lastSyncTime, false)
|
|
1326
1334
|
const data = (syncResponse.data || syncResponse) as SyncManifestData
|
|
1327
1335
|
if (data.captures) {
|
|
@@ -1915,6 +1923,7 @@ class GallerySyncService {
|
|
|
1915
1923
|
bracketPaths: result.bracketPaths,
|
|
1916
1924
|
sidecarPath: result.sidecarPath,
|
|
1917
1925
|
thumbnailData: capture.thumbnail_data,
|
|
1926
|
+
thumbnailPath: result.thumbnailPath,
|
|
1918
1927
|
captureDir: result.captureDir,
|
|
1919
1928
|
timestamp: capture.timestamp,
|
|
1920
1929
|
totalSize: capture.total_size,
|
|
@@ -2251,7 +2260,7 @@ class GallerySyncService {
|
|
|
2251
2260
|
|
|
2252
2261
|
/** True while pre-flight startSync work is in flight (before sync state transitions). */
|
|
2253
2262
|
isSyncStarting(): boolean {
|
|
2254
|
-
return
|
|
2263
|
+
return useGallerySyncStore.getState().syncStarting
|
|
2255
2264
|
}
|
|
2256
2265
|
|
|
2257
2266
|
/**
|
|
@@ -12,17 +12,12 @@ let nextJobId = 1_000_000
|
|
|
12
12
|
const nativeJobs = new Map<number, string>()
|
|
13
13
|
const cancelledNativeJobs = new Set<number>()
|
|
14
14
|
|
|
15
|
-
export function shouldUseScopedLocalNetwork(
|
|
16
|
-
os
|
|
17
|
-
platformVersion: number | string,
|
|
18
|
-
moduleAvailable: boolean,
|
|
19
|
-
): boolean {
|
|
20
|
-
const level = typeof platformVersion === "number" ? platformVersion : Number.parseInt(platformVersion, 10)
|
|
21
|
-
return os === "android" && level >= 29 && moduleAvailable
|
|
15
|
+
export function shouldUseScopedLocalNetwork(os: string, moduleAvailable: boolean): boolean {
|
|
16
|
+
return os === "android" && moduleAvailable
|
|
22
17
|
}
|
|
23
18
|
|
|
24
19
|
function supportsScopedConnection(): boolean {
|
|
25
|
-
return shouldUseScopedLocalNetwork(Platform.OS,
|
|
20
|
+
return shouldUseScopedLocalNetwork(Platform.OS, MentraLocalNetwork != null)
|
|
26
21
|
}
|
|
27
22
|
|
|
28
23
|
function normalizeHeaders(headers?: HeadersInit): Record<string, string> {
|
|
@@ -46,15 +41,16 @@ export const localNetworkTransport = {
|
|
|
46
41
|
supportsScopedConnection: (): boolean => supportsScopedConnection(),
|
|
47
42
|
isScopedConnectionActive: (): boolean => scopedConnectionActive,
|
|
48
43
|
|
|
49
|
-
async connect(ssid: string, password: string): Promise<
|
|
44
|
+
async connect(ssid: string, password: string): Promise<string | undefined> {
|
|
50
45
|
scopedConnectionActive = false
|
|
51
46
|
nativeJobs.clear()
|
|
52
47
|
if (!supportsScopedConnection()) {
|
|
53
48
|
await WifiManager.connectToProtectedSSID(ssid, password, false, false)
|
|
54
|
-
return
|
|
49
|
+
return undefined
|
|
55
50
|
}
|
|
56
|
-
await MentraLocalNetwork!.connect(ssid, password)
|
|
51
|
+
const result = await MentraLocalNetwork!.connect(ssid, password)
|
|
57
52
|
scopedConnectionActive = true
|
|
53
|
+
return result.localAddress
|
|
58
54
|
},
|
|
59
55
|
|
|
60
56
|
async disconnect(): Promise<void> {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type {AudioSource} from "expo-audio"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A tiny bundled source used to detach completed or interrupted audio without
|
|
5
|
+
* destroying the reusable native player. If a media control later sends Play,
|
|
6
|
+
* only this silence can be resumed instead of the previous miniapp audio.
|
|
7
|
+
*/
|
|
8
|
+
export const SILENT_AUDIO_SOURCE: AudioSource = require("../assets/silence.wav")
|
|
@@ -6,7 +6,7 @@ import BluetoothSdk from "@mentra/bluetooth-sdk/internal"
|
|
|
6
6
|
import {BgTimer} from "../utils/timers"
|
|
7
7
|
import {useGlassesStore} from "../stores/glasses"
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import {resolveOtaManifestUrl} from "./otaManifestUrl"
|
|
10
10
|
import {detectClockSkew} from "./gallerySyncClock"
|
|
11
11
|
|
|
12
12
|
export const CLOCK_SETTLE_MS = 500
|
|
@@ -86,7 +86,7 @@ export async function handleOtaClockSkewFromGlasses(
|
|
|
86
86
|
|
|
87
87
|
lastOtaClockFixAt = Date.now()
|
|
88
88
|
const {buildNumber, otaVersionUrl} = useGlassesStore.getState()
|
|
89
|
-
const manifestUrl =
|
|
89
|
+
const manifestUrl = resolveOtaManifestUrl(otaVersionUrl, buildNumber)
|
|
90
90
|
console.log("[GlassesClockSync] ⏰ Restarting OTA with ota_start after clock fix")
|
|
91
91
|
await BluetoothSdk.startOtaUpdate(manifestUrl)
|
|
92
92
|
return true
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type {DeclaredAction} from "../types/applet"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Normalize manifest actions defensively for installed and dev-sideloaded
|
|
5
|
+
* bundles. Input and output schemas are passed through as opaque JSON Schema.
|
|
6
|
+
*/
|
|
7
|
+
export function normalizeManifestActions(raw: unknown): DeclaredAction[] {
|
|
8
|
+
if (!Array.isArray(raw)) return []
|
|
9
|
+
const out: DeclaredAction[] = []
|
|
10
|
+
for (const a of raw as Array<{
|
|
11
|
+
id?: unknown
|
|
12
|
+
description?: unknown
|
|
13
|
+
parameters?: unknown
|
|
14
|
+
outputSchema?: unknown
|
|
15
|
+
}>) {
|
|
16
|
+
if (a && typeof a.id === "string" && typeof a.description === "string") {
|
|
17
|
+
out.push({
|
|
18
|
+
id: a.id,
|
|
19
|
+
description: a.description,
|
|
20
|
+
...(a.parameters && typeof a.parameters === "object" && !Array.isArray(a.parameters)
|
|
21
|
+
? {parameters: a.parameters as Record<string, unknown>}
|
|
22
|
+
: {}),
|
|
23
|
+
...(a.outputSchema && typeof a.outputSchema === "object" && !Array.isArray(a.outputSchema)
|
|
24
|
+
? {outputSchema: a.outputSchema as Record<string, unknown>}
|
|
25
|
+
: {}),
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return out
|
|
30
|
+
}
|
|
@@ -18,38 +18,80 @@ export type DisplayState = "starting" | "updating" | "complete" | "failed" | "di
|
|
|
18
18
|
*
|
|
19
19
|
* Priority-ordered rules (first match wins):
|
|
20
20
|
* 1. errorMsg !== "" -> "failed"
|
|
21
|
-
* 2. BES
|
|
22
|
-
* 3. BES
|
|
21
|
+
* 2. BES reboot recovery complete -> "complete"
|
|
22
|
+
* 3. BES reboot recovery awaiting reconnect -> "restarting"
|
|
23
|
+
* 4. BES terminal (any connection state) -> "restarting"
|
|
23
24
|
* (legacy-shaped BES "complete" counts only in install phase)
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
25
|
+
* 6. apkCompletedViaBuildIncrease (legacy build-number port) -> "complete"
|
|
26
|
+
* 7. otaStatus.status === "failed" -> "failed"
|
|
27
|
+
* 8. otaStatus.status === "complete" (non-BES) -> "complete"
|
|
27
28
|
* (unless legacy-shaped download-complete or held apk settle -> in flight)
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
29
|
+
* 9. !connected + legacy in-flight complete (held apk) -> "updating"
|
|
30
|
+
* 10. !connected + APK step_complete with totalSteps > 1 -> "starting" (expected reboot)
|
|
31
|
+
* 11. !connected + BES in flight -> "restarting" (defensive)
|
|
32
|
+
* 12. !connected -> "disconnected"
|
|
33
|
+
* 13. connected + in_progress | step_complete | legacy in-flight complete -> "updating"
|
|
34
|
+
* 14. fallback -> "starting"
|
|
34
35
|
*/
|
|
35
36
|
export function deriveDisplayState(args: {
|
|
36
37
|
otaStatus: OtaStatus | null
|
|
37
38
|
otaProgress: OtaProgress | null
|
|
38
39
|
connected: boolean
|
|
39
40
|
errorMsg: string
|
|
41
|
+
/**
|
|
42
|
+
* Retained for host API compatibility. A session-wide reconnect edge is not
|
|
43
|
+
* sufficient for BES completion because it may belong to an earlier APK step.
|
|
44
|
+
*/
|
|
40
45
|
sawReconnectEdge: boolean
|
|
41
46
|
/** Coordinator is holding a legacy apk install "complete" for the settle window (WP 8C). */
|
|
42
47
|
legacyApkSettleHold?: boolean
|
|
43
48
|
/** Legacy APK completion detected by build-number increase (WP 8C). */
|
|
44
49
|
apkCompletedViaBuildIncrease?: boolean
|
|
50
|
+
/** Phone-observed BES disconnect/reconnect recovery, independent of stale OTA progress. */
|
|
51
|
+
besRestartRecovery?: "awaiting" | "complete" | null
|
|
52
|
+
/** Downgrade detour: the reconnected glasses reported exactly the pinned target version. */
|
|
53
|
+
versionChangeConverged?: boolean
|
|
54
|
+
/** True for the whole downgrade (version-change) session. */
|
|
55
|
+
versionChangeSession?: boolean
|
|
45
56
|
}): DisplayState {
|
|
46
|
-
const {
|
|
47
|
-
|
|
57
|
+
const {
|
|
58
|
+
otaStatus,
|
|
59
|
+
otaProgress,
|
|
60
|
+
connected,
|
|
61
|
+
errorMsg,
|
|
62
|
+
legacyApkSettleHold,
|
|
63
|
+
apkCompletedViaBuildIncrease,
|
|
64
|
+
besRestartRecovery,
|
|
65
|
+
versionChangeConverged,
|
|
66
|
+
versionChangeSession,
|
|
67
|
+
} = args
|
|
48
68
|
|
|
49
69
|
if (errorMsg) return "failed"
|
|
50
70
|
|
|
71
|
+
// Downgrade detour completion: exact-version convergence after reconnect is the
|
|
72
|
+
// only completion signal (the wipe destroys ASG's ota_session, and the target is
|
|
73
|
+
// a LOWER build so no build-number increase fires). Outranks the stale install
|
|
74
|
+
// status still sitting in the store from before the handoff.
|
|
75
|
+
if (versionChangeConverged) return "complete"
|
|
76
|
+
|
|
77
|
+
// A BES reboot destroys or strands the glasses-side OTA session on older
|
|
78
|
+
// clients. Once the phone observed that reboot, stale pre-reboot progress must
|
|
79
|
+
// not move the UI from restarting back to updating.
|
|
80
|
+
if (besRestartRecovery === "complete") return "complete"
|
|
81
|
+
if (besRestartRecovery === "awaiting") return "restarting"
|
|
82
|
+
|
|
83
|
+
// In a version-change session, convergence (above) is the ONLY completion for the ASG
|
|
84
|
+
// downgrade. A bare non-firmware "complete" before convergence — the glasses refusing
|
|
85
|
+
// the downgrade (floor/gate) with no install, or a stale pre-handoff status — must never
|
|
86
|
+
// present as success. Firmware-step completions (mtk/bes) DO pass through: a combined
|
|
87
|
+
// downgrade+firmware manifest applies firmware first on its own pass, which must finish
|
|
88
|
+
// so the phone re-checks and re-offers the now-firmware-free downgrade.
|
|
89
|
+
const isFirmwareComplete = otaStatus?.stepType === "mtk" || otaStatus?.stepType === "bes"
|
|
90
|
+
if (versionChangeSession && otaStatus?.status === "complete" && !isFirmwareComplete) {
|
|
91
|
+
return connected ? "updating" : "disconnected"
|
|
92
|
+
}
|
|
93
|
+
|
|
51
94
|
const besTerminal = isBesTerminal(otaStatus, otaProgress)
|
|
52
|
-
if (besTerminal && connected && sawReconnectEdge) return "complete"
|
|
53
95
|
if (besTerminal) return "restarting"
|
|
54
96
|
|
|
55
97
|
// Legacy build-number APK completion: the coordinator clears errorMsg when it fires,
|
|
@@ -85,8 +127,7 @@ export function deriveDisplayState(args: {
|
|
|
85
127
|
// has not started); the legacy screen treated that drop as a plain disconnect.
|
|
86
128
|
const besStatusInFlight =
|
|
87
129
|
otaStatus?.stepType === "bes" && !(isLegacyShapedOtaStatus(otaStatus) && otaStatus.phase === "download")
|
|
88
|
-
const besInFlight =
|
|
89
|
-
besStatusInFlight || (otaProgress?.currentUpdate === "bes" && otaProgress?.stage === "install")
|
|
130
|
+
const besInFlight = besStatusInFlight || (otaProgress?.currentUpdate === "bes" && otaProgress?.stage === "install")
|
|
90
131
|
if (besInFlight) return "restarting"
|
|
91
132
|
|
|
92
133
|
return "disconnected"
|
|
@@ -10,6 +10,7 @@ import type {OtaProgress, OtaStatus} from "@mentra/bluetooth-sdk/internal"
|
|
|
10
10
|
export const MINIMUM_OTA_STATUS_BUILD = 37
|
|
11
11
|
|
|
12
12
|
export const MAX_RETRIES = 3
|
|
13
|
+
/** Wait between ended native ota_start requests; never overlaps a pending request. */
|
|
13
14
|
export const RETRY_INTERVAL_MS = 5000
|
|
14
15
|
/** APK/BES and general install when progress events are expected */
|
|
15
16
|
export const PROGRESS_TIMEOUT_MS = 120_000
|
|
@@ -19,8 +20,6 @@ export const DOWNLOAD_STUCK_TIMEOUT_MS = 70_000
|
|
|
19
20
|
export const MTK_INSTALL_TIMEOUT_MS = 300_000
|
|
20
21
|
/** Whole multi-step session cap */
|
|
21
22
|
export const GLOBAL_OTA_TIMEOUT_MS = 20 * 60 * 1000
|
|
22
|
-
/** After APK reboot, ASG OTA service needs time before next ota_start */
|
|
23
|
-
export const POST_APK_OTA_START_DELAY_MS = 6000
|
|
24
23
|
/** BLE keepalive during OTA */
|
|
25
24
|
export const PING_INTERVAL_MS = 10_000
|
|
26
25
|
/**
|
|
@@ -71,6 +70,8 @@ export const LEGACY_MTK_STALL_ZONE_MAX_PERCENT = 55
|
|
|
71
70
|
export const LEGACY_MTK_SIM_FLOOR_PERCENT = 51
|
|
72
71
|
export const LEGACY_MTK_SIM_CAP_PERCENT = 60
|
|
73
72
|
|
|
73
|
+
export type OtaProtocolProfile = "legacy" | "unified"
|
|
74
|
+
|
|
74
75
|
/**
|
|
75
76
|
* Legacy-shaped ota_status: old (< 37) ASG builds send `ota_progress`, which the SDK
|
|
76
77
|
* (Android MentraLive.kt / iOS MentraLive.swift) maps to a unified ota_status with
|
|
@@ -98,9 +99,34 @@ export function isLegacyShapedOtaSession(
|
|
|
98
99
|
return Number.isFinite(build) && build > 0 && build < MINIMUM_OTA_STATUS_BUILD
|
|
99
100
|
}
|
|
100
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Select the compatibility profile once at install-screen attach. The old split
|
|
104
|
+
* routes made this decision from the pre-update build and kept it for the whole
|
|
105
|
+
* mounted flow, even when an APK step restarted into a newer ASG build. Returning
|
|
106
|
+
* null leaves the coordinator undecided until the first meaningful OTA event.
|
|
107
|
+
*/
|
|
108
|
+
export function selectOtaProtocolProfile(
|
|
109
|
+
otaStatus: OtaStatus | null,
|
|
110
|
+
otaProgress: OtaProgress | null,
|
|
111
|
+
buildNumber: string | null | undefined,
|
|
112
|
+
): OtaProtocolProfile | null {
|
|
113
|
+
const build = Number.parseInt(buildNumber ?? "", 10)
|
|
114
|
+
if (Number.isFinite(build) && build > 0) {
|
|
115
|
+
return build < MINIMUM_OTA_STATUS_BUILD ? "legacy" : "unified"
|
|
116
|
+
}
|
|
117
|
+
if (otaStatus) return isLegacyShapedOtaStatus(otaStatus) ? "legacy" : "unified"
|
|
118
|
+
if (otaProgress) return "legacy"
|
|
119
|
+
return null
|
|
120
|
+
}
|
|
121
|
+
|
|
101
122
|
export const OtaProgressMessages = {
|
|
102
|
-
noAckResponse: "Unable to start update. Glasses did not respond.",
|
|
103
123
|
stalledOrStuck: "Update may have failed. Ensure glasses have internet access and try again.",
|
|
104
124
|
globalTimeout: "Update took too long. Please try again.",
|
|
105
125
|
sendOtaStartFailed: "Failed to communicate with glasses.",
|
|
126
|
+
hotspotArtifactDownloadFailed: "Could not download the update to your phone. Check phone internet and try again.",
|
|
127
|
+
hotspotArtifactVerifyFailed: "The update package could not be verified. Please try again later.",
|
|
128
|
+
hotspotWifiPermissionDenied: "Allow nearby devices access so the phone can connect to the glasses hotspot.",
|
|
129
|
+
hotspotStartFailed: "Could not start the glasses hotspot. Please try again.",
|
|
130
|
+
hotspotJoinFailed: "Could not connect the phone to the glasses hotspot. Please try again.",
|
|
131
|
+
hotspotServerFailed: "Could not make the update available to the glasses. Please try again.",
|
|
106
132
|
} as const
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
+
import {sdkPinnedOtaManifestUrl} from "@mentra/bluetooth-sdk/internal"
|
|
2
|
+
|
|
1
3
|
import {SETTINGS, useSettingsStore} from "../stores/settings"
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
// Legacy production manifest — pre-39 glasses install from this (they ignore ota_start's URL).
|
|
6
|
+
const OTA_VERSION_URL_LEGACY_PROD = "https://ota.mentraglass.com/prod_live_version.json"
|
|
7
|
+
// Modern production fleet manifest (v2). Only a last-resort fallback when the SDK-derived pin
|
|
8
|
+
// URL is somehow unavailable; modern glasses honor ota_start's URL, so this must be the v2
|
|
9
|
+
// artifact, not the legacy v1 rescue manifest.
|
|
10
|
+
const OTA_VERSION_URL_PROD = "https://ota.mentraglass.com/prod_live_version_v2.json"
|
|
4
11
|
|
|
5
12
|
function isLegacyAsgOtaStartBuild(glassesBuildNumber?: string | null): boolean {
|
|
6
13
|
const buildNumber = Number.parseInt(glassesBuildNumber ?? "", 10)
|
|
@@ -20,12 +27,12 @@ function getOtaVersionUrlDevOverride(): string | null {
|
|
|
20
27
|
return trimmed || null
|
|
21
28
|
}
|
|
22
29
|
|
|
23
|
-
export function
|
|
30
|
+
export function resolveOtaManifestUrl(glassesUrl?: string | null, glassesBuildNumber?: string | null): string {
|
|
24
31
|
const deviceUrl = glassesUrl?.trim()
|
|
25
32
|
if (isLegacyAsgOtaStartBuild(glassesBuildNumber)) {
|
|
26
33
|
// Legacy glasses ignore ota_start.ota_version_url and install from their
|
|
27
34
|
// compiled default, so the developer override does not apply to them either.
|
|
28
|
-
return deviceUrl ||
|
|
35
|
+
return deviceUrl || OTA_VERSION_URL_LEGACY_PROD
|
|
29
36
|
}
|
|
30
37
|
|
|
31
38
|
const devOverrideUrl = getOtaVersionUrlDevOverride()
|
|
@@ -37,5 +44,10 @@ export function getAsgOtaVersionUrl(glassesUrl?: string | null, glassesBuildNumb
|
|
|
37
44
|
if (envUrl) {
|
|
38
45
|
return envUrl
|
|
39
46
|
}
|
|
40
|
-
|
|
47
|
+
|
|
48
|
+
// The phone owns manifest selection: modern glasses are driven with the manifest pinned to
|
|
49
|
+
// this app's Bluetooth SDK version. Modern ASG builds report no manifest URL of their own;
|
|
50
|
+
// a glasses-reported URL (older modern builds advertising their baked fleet default) is only
|
|
51
|
+
// a last resort when the SDK version is somehow unavailable.
|
|
52
|
+
return sdkPinnedOtaManifestUrl() || deviceUrl || OTA_VERSION_URL_PROD
|
|
41
53
|
}
|