@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
|
@@ -12,12 +12,27 @@
|
|
|
12
12
|
* get it in the same round trip — no second fetch.
|
|
13
13
|
*
|
|
14
14
|
* Pre-flighting at the call site keeps /applet/local a pure mount route.
|
|
15
|
+
*
|
|
16
|
+
* When the laptop's Wi-Fi IP changes, the URL stored from the last QR scan
|
|
17
|
+
* goes stale. We then retry alternate hosts (Metro / EXPO_PUBLIC_LOCAL_MINIAPP_HOST,
|
|
18
|
+
* plus an mDNS name captured at scan time) and persist the working URL so
|
|
19
|
+
* the next launch doesn't need a re-scan.
|
|
15
20
|
*/
|
|
16
21
|
|
|
22
|
+
import {configuredDevHost} from "./configuredDevHost"
|
|
17
23
|
import {storage} from "./storage/storage"
|
|
18
24
|
|
|
19
25
|
const REACHABILITY_TIMEOUT_MS = 1500
|
|
20
26
|
|
|
27
|
+
// A single dropped DNS lookup or momentary Wi-Fi blip (AP roam, DHCP renewal,
|
|
28
|
+
// phone hopping to cellular for a beat) looks identical to a dead dev server —
|
|
29
|
+
// both throw out of fetch(). Retrying once absorbs that transient case instead
|
|
30
|
+
// of bouncing straight to the offline screen. We do NOT retry a real HTTP
|
|
31
|
+
// response (res.ok === false): that's the server telling us something
|
|
32
|
+
// definitive, not a network-layer hiccup.
|
|
33
|
+
const REACHABILITY_MAX_ATTEMPTS = 2
|
|
34
|
+
const REACHABILITY_RETRY_DELAY_MS = 400
|
|
35
|
+
|
|
21
36
|
export type DevManifest = {
|
|
22
37
|
packageName?: string
|
|
23
38
|
name?: string
|
|
@@ -29,40 +44,153 @@ export type DevManifest = {
|
|
|
29
44
|
}
|
|
30
45
|
|
|
31
46
|
export type DevLaunchResult =
|
|
32
|
-
| {decision: "live"; manifest: DevManifest}
|
|
33
|
-
| {decision: "offline"; manifest: null}
|
|
47
|
+
| {decision: "live"; manifest: DevManifest; resolvedUrl: string}
|
|
48
|
+
| {decision: "offline"; manifest: null; resolvedUrl?: undefined}
|
|
49
|
+
|
|
50
|
+
export type DecideDevLaunchOptions = {
|
|
51
|
+
/** Extra hostnames / IPs to try if `devUrl` is unreachable (no scheme/port). */
|
|
52
|
+
alternateHosts?: string[]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function delay(ms: number): Promise<void> {
|
|
56
|
+
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Single GET <devUrl>/miniapp.json attempt with a hard timeout. */
|
|
60
|
+
async function fetchManifestOnce(devUrl: string): Promise<DevManifest | null> {
|
|
61
|
+
const controller = new AbortController()
|
|
62
|
+
const timer = setTimeout(() => controller.abort(), REACHABILITY_TIMEOUT_MS)
|
|
63
|
+
try {
|
|
64
|
+
const res = await fetch(`${devUrl.replace(/\/$/, "")}/miniapp.json`, {
|
|
65
|
+
method: "GET",
|
|
66
|
+
signal: controller.signal,
|
|
67
|
+
})
|
|
68
|
+
if (!res.ok) return null
|
|
69
|
+
return (await res.json()) as DevManifest
|
|
70
|
+
} finally {
|
|
71
|
+
clearTimeout(timer)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function rewriteHost(devUrl: string, host: string): string | null {
|
|
76
|
+
const trimmed = host.trim()
|
|
77
|
+
if (!trimmed) return null
|
|
78
|
+
try {
|
|
79
|
+
const url = new URL(devUrl)
|
|
80
|
+
if (url.hostname === trimmed) return null
|
|
81
|
+
url.hostname = trimmed
|
|
82
|
+
return url.toString().replace(/\/$/, "")
|
|
83
|
+
} catch {
|
|
84
|
+
return null
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Point an http(s) icon at the host that actually answered. Cached dev icons are
|
|
90
|
+
* local `file://` paths and third-party icons live on unrelated hosts, so only
|
|
91
|
+
* icons still served by the stale dev host are rewritten.
|
|
92
|
+
*/
|
|
93
|
+
function rewriteIconHost(iconUrl: string, staleDevUrl: string, resolvedDevUrl: string): string | null {
|
|
94
|
+
try {
|
|
95
|
+
const icon = new URL(iconUrl)
|
|
96
|
+
if (icon.protocol !== "http:" && icon.protocol !== "https:") return null
|
|
97
|
+
const stale = new URL(staleDevUrl)
|
|
98
|
+
const resolved = new URL(resolvedDevUrl)
|
|
99
|
+
// Compare host (hostname+port) so an icon on the same hostname but a
|
|
100
|
+
// different service port is left alone.
|
|
101
|
+
if (icon.host !== stale.host) return null
|
|
102
|
+
icon.host = resolved.host
|
|
103
|
+
return icon.toString()
|
|
104
|
+
} catch {
|
|
105
|
+
return null
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function collectAlternateHosts(packageName: string, extra?: string[]): string[] {
|
|
110
|
+
const hosts: string[] = []
|
|
111
|
+
const push = (h: string | undefined | null) => {
|
|
112
|
+
const v = h?.trim()
|
|
113
|
+
if (v && !hosts.includes(v)) hosts.push(v)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
push(configuredDevHost())
|
|
117
|
+
|
|
118
|
+
if (packageName) {
|
|
119
|
+
const mdns = storage.load<string>(`${packageName}_dev_mdns`)
|
|
120
|
+
if (mdns.is_ok()) push(mdns.value)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
for (const h of extra ?? []) push(h)
|
|
124
|
+
return hosts
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function probeUrl(devUrl: string): Promise<DevManifest | null | "network-error"> {
|
|
128
|
+
for (let attempt = 1; attempt <= REACHABILITY_MAX_ATTEMPTS; attempt++) {
|
|
129
|
+
try {
|
|
130
|
+
const manifest = await fetchManifestOnce(devUrl)
|
|
131
|
+
// Definitive HTTP failure — do not retry this URL further.
|
|
132
|
+
if (!manifest) return null
|
|
133
|
+
return manifest
|
|
134
|
+
} catch {
|
|
135
|
+
if (attempt < REACHABILITY_MAX_ATTEMPTS) {
|
|
136
|
+
await delay(REACHABILITY_RETRY_DELAY_MS)
|
|
137
|
+
continue
|
|
138
|
+
}
|
|
139
|
+
return "network-error"
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return "network-error"
|
|
143
|
+
}
|
|
34
144
|
|
|
35
145
|
/**
|
|
36
|
-
* GET <devUrl>/miniapp.json with a hard timeout
|
|
146
|
+
* GET <devUrl>/miniapp.json with a hard timeout, retrying once on a
|
|
147
|
+
* network-layer failure (DNS/connection/abort) before trying alternate
|
|
148
|
+
* hosts (Metro laptop IP, mDNS name from the QR). Returns the parsed
|
|
37
149
|
* manifest on success ("live") or null ("offline").
|
|
38
150
|
*
|
|
39
|
-
* Side
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* The fetch doubles as the reachability probe AND the manifest source —
|
|
44
|
-
* one request per launch attempt instead of two.
|
|
151
|
+
* Side effects on success:
|
|
152
|
+
* - writes <packageName>_dev_last_reachable
|
|
153
|
+
* - if an alternate host worked, rewrites <packageName>_dev_url so the
|
|
154
|
+
* next launch skips the stale IP
|
|
45
155
|
*/
|
|
46
156
|
export async function decideDevLaunchRoute(
|
|
47
157
|
packageName: string,
|
|
48
158
|
devUrl: string,
|
|
159
|
+
options?: DecideDevLaunchOptions,
|
|
49
160
|
): Promise<DevLaunchResult> {
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
161
|
+
const candidates: string[] = [devUrl]
|
|
162
|
+
for (const host of collectAlternateHosts(packageName, options?.alternateHosts)) {
|
|
163
|
+
const rewritten = rewriteHost(devUrl, host)
|
|
164
|
+
if (rewritten && !candidates.includes(rewritten)) candidates.push(rewritten)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
for (const candidate of candidates) {
|
|
168
|
+
const result = await probeUrl(candidate)
|
|
169
|
+
if (result === "network-error" || result === null) continue
|
|
170
|
+
|
|
59
171
|
if (packageName) {
|
|
60
172
|
storage.save(`${packageName}_dev_last_reachable`, Date.now())
|
|
173
|
+
if (candidate !== devUrl) {
|
|
174
|
+
storage.save(`${packageName}_dev_url`, candidate)
|
|
175
|
+
// Keep _dev_meta.devUrl in sync when present so home-tile reads match.
|
|
176
|
+
const metaRes = storage.load<string>(`${packageName}_dev_meta`)
|
|
177
|
+
if (metaRes.is_ok()) {
|
|
178
|
+
try {
|
|
179
|
+
const meta = JSON.parse(metaRes.value) as {devUrl?: string; iconUrl?: string}
|
|
180
|
+
meta.devUrl = candidate
|
|
181
|
+
if (typeof meta.iconUrl === "string") {
|
|
182
|
+
const rewritten = rewriteIconHost(meta.iconUrl, devUrl, candidate)
|
|
183
|
+
if (rewritten) meta.iconUrl = rewritten
|
|
184
|
+
}
|
|
185
|
+
storage.save(`${packageName}_dev_meta`, JSON.stringify(meta))
|
|
186
|
+
} catch {
|
|
187
|
+
/* ignore malformed meta */
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
61
191
|
}
|
|
62
|
-
return {decision: "live", manifest}
|
|
63
|
-
} catch {
|
|
64
|
-
return {decision: "offline", manifest: null}
|
|
65
|
-
} finally {
|
|
66
|
-
clearTimeout(timer)
|
|
192
|
+
return {decision: "live", manifest: result, resolvedUrl: candidate}
|
|
67
193
|
}
|
|
194
|
+
|
|
195
|
+
return {decision: "offline", manifest: null}
|
|
68
196
|
}
|
|
@@ -4,11 +4,14 @@ import * as Location from "expo-location"
|
|
|
4
4
|
import {Platform} from "react-native"
|
|
5
5
|
import type {ReportContext} from "@mentra/cloud-client"
|
|
6
6
|
|
|
7
|
-
import
|
|
7
|
+
import appRegistry from "../services/AppRegistry"
|
|
8
|
+
import localMiniappRuntime from "../services/LocalMiniappRuntime"
|
|
9
|
+
import {getLastOpenTime, useAppStatusStore} from "../stores/apps"
|
|
8
10
|
import {useConnectionStore} from "../stores/connection"
|
|
9
11
|
import {useCoreStore} from "../stores/core"
|
|
10
12
|
import {useGlassesStore} from "../stores/glasses"
|
|
11
13
|
import {SETTINGS, useSettingsStore} from "../stores/settings"
|
|
14
|
+
import {buildMiniappDiagnosticContext} from "./miniappDiagnostics"
|
|
12
15
|
|
|
13
16
|
const SENSITIVE_SETTINGS_KEYS = ["core_token", "auth_token", "auth_email"] as const
|
|
14
17
|
const SENSITIVE_GLASSES_KEYS = ["hotspotPassword"] as const
|
|
@@ -108,17 +111,17 @@ export async function collectDiagnosticContext(extra?: Partial<ReportContext>):
|
|
|
108
111
|
|
|
109
112
|
const offlineMode = await useSettingsStore.getState().getSetting(SETTINGS.offline_mode.key)
|
|
110
113
|
const defaultWearable = await useSettingsStore.getState().getSetting(SETTINGS.default_wearable.key)
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
})
|
|
114
|
+
const miniappRuntime = localMiniappRuntime.getDiagnosticSnapshot()
|
|
115
|
+
const miniapps = buildMiniappDiagnosticContext({
|
|
116
|
+
apps: appletState.apps,
|
|
117
|
+
foregroundedPackage: appletState.foregroundedPackage,
|
|
118
|
+
runtime: miniappRuntime,
|
|
119
|
+
getLastOpenedAtMs: (app) => getLastOpenTime(app.packageName),
|
|
120
|
+
getManifest: (app) =>
|
|
121
|
+
app.local && app.version ? appRegistry.getMiniappManifest(app.packageName, app.version) : undefined,
|
|
122
|
+
getReleaseIdentity: (app) =>
|
|
123
|
+
app.local && app.version ? appRegistry.getReleaseIdentity(app.packageName, app.version) : undefined,
|
|
124
|
+
})
|
|
122
125
|
|
|
123
126
|
return {
|
|
124
127
|
app: {
|
|
@@ -139,12 +142,9 @@ export async function collectDiagnosticContext(extra?: Partial<ReportContext>):
|
|
|
139
142
|
runtime: {
|
|
140
143
|
core: coreState,
|
|
141
144
|
connection: connectionState,
|
|
145
|
+
miniapps: miniappRuntime,
|
|
142
146
|
},
|
|
143
|
-
apps:
|
|
144
|
-
apps,
|
|
145
|
-
installed: apps.map((app) => app.packageName),
|
|
146
|
-
running: apps.filter((app) => app.running).map((app) => app.packageName),
|
|
147
|
-
},
|
|
147
|
+
apps: miniapps,
|
|
148
148
|
settings: {
|
|
149
149
|
...filteredSettings,
|
|
150
150
|
offlineMode: !!offlineMode,
|
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Capabilities,
|
|
3
|
-
HardwareRequirement,
|
|
4
|
-
HardwareType,
|
|
5
|
-
HardwareRequirementLevel,
|
|
6
|
-
DeviceTypes,
|
|
7
|
-
} from "../../types"
|
|
8
|
-
import {simulatedGlasses} from "../../types"
|
|
1
|
+
import {Capabilities, DeviceTypes, HardwareRequirement, HardwareRequirementLevel, HardwareType, simulatedGlasses} from "../../types"
|
|
9
2
|
|
|
10
3
|
/**
|
|
11
4
|
* Result of a hardware compatibility check
|
|
@@ -86,7 +79,12 @@ export class HardwareCompatibility {
|
|
|
86
79
|
return capabilities.hasDisplay
|
|
87
80
|
|
|
88
81
|
case HardwareType.MICROPHONE:
|
|
89
|
-
|
|
82
|
+
// MentraOS always provides an audio-input path. When the connected
|
|
83
|
+
// glasses do not have an onboard microphone, the engine falls back to
|
|
84
|
+
// the phone microphone. Keep `capabilities.hasMicrophone` reserved for
|
|
85
|
+
// physical-device/source selection; app compatibility is about whether
|
|
86
|
+
// the requested input is available to the miniapp.
|
|
87
|
+
return true
|
|
90
88
|
|
|
91
89
|
case HardwareType.SPEAKER:
|
|
92
90
|
return capabilities.hasSpeaker
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import type {ClientApp} from "../types/applet"
|
|
2
|
+
|
|
3
|
+
const MAX_DIAGNOSTIC_STRING_LENGTH = 512
|
|
4
|
+
const MAX_DIAGNOSTIC_LIST_ITEMS = 100
|
|
5
|
+
const MAX_RECENT_MINIAPPS = 20
|
|
6
|
+
|
|
7
|
+
export interface MiniappManifestSnapshot {
|
|
8
|
+
packageName: string
|
|
9
|
+
name?: string
|
|
10
|
+
version?: string
|
|
11
|
+
sdkVersion?: string
|
|
12
|
+
minHostVersion?: string
|
|
13
|
+
type?: string
|
|
14
|
+
entry?: {
|
|
15
|
+
background?: string
|
|
16
|
+
ui?: string
|
|
17
|
+
}
|
|
18
|
+
permissions: Array<{type: string; required?: boolean}>
|
|
19
|
+
hardwareRequirements: Array<{type: string; level: string}>
|
|
20
|
+
actionIds: string[]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface MiniappRuntimeAppSnapshot {
|
|
24
|
+
packageName: string
|
|
25
|
+
handshakeComplete: boolean
|
|
26
|
+
subscriptions: string[]
|
|
27
|
+
lastMessageAgeMs: number
|
|
28
|
+
requestedLocationRate: string | null
|
|
29
|
+
hasActiveSpeakerStream: boolean
|
|
30
|
+
manifest?: MiniappManifestSnapshot
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface MiniappRuntimeDiagnosticSnapshot {
|
|
34
|
+
connectedApps: MiniappRuntimeAppSnapshot[]
|
|
35
|
+
subscriptionsByStream: Record<string, string[]>
|
|
36
|
+
resources: {
|
|
37
|
+
display: Record<string, unknown>
|
|
38
|
+
camera: Record<string, unknown>
|
|
39
|
+
video: Record<string, unknown>
|
|
40
|
+
stream: Record<string, unknown>
|
|
41
|
+
cameraFov: Record<string, unknown>
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
type ManifestFallback = {
|
|
46
|
+
packageName: string
|
|
47
|
+
name?: string
|
|
48
|
+
version?: string
|
|
49
|
+
type?: string
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function boundedString(value: unknown): string | undefined {
|
|
53
|
+
if (typeof value !== "string") return undefined
|
|
54
|
+
const trimmed = value.trim()
|
|
55
|
+
if (!trimmed) return undefined
|
|
56
|
+
return trimmed.slice(0, MAX_DIAGNOSTIC_STRING_LENGTH)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function recordValue(value: unknown): Record<string, unknown> | null {
|
|
60
|
+
return value && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : null
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function diagnosticPermissions(value: unknown): Array<{type: string; required?: boolean}> {
|
|
64
|
+
if (!Array.isArray(value)) return []
|
|
65
|
+
const permissions: Array<{type: string; required?: boolean}> = []
|
|
66
|
+
for (const raw of value) {
|
|
67
|
+
if (typeof raw === "string") {
|
|
68
|
+
const type = boundedString(raw)
|
|
69
|
+
if (type) permissions.push({type})
|
|
70
|
+
continue
|
|
71
|
+
}
|
|
72
|
+
const permission = recordValue(raw)
|
|
73
|
+
const type = boundedString(permission?.type)
|
|
74
|
+
if (!type) continue
|
|
75
|
+
permissions.push({
|
|
76
|
+
type,
|
|
77
|
+
...(typeof permission?.required === "boolean" ? {required: permission.required} : {}),
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
return permissions.slice(0, MAX_DIAGNOSTIC_LIST_ITEMS)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function diagnosticHardwareRequirements(value: unknown): Array<{type: string; level: string}> {
|
|
84
|
+
if (!Array.isArray(value)) return []
|
|
85
|
+
const requirements: Array<{type: string; level: string}> = []
|
|
86
|
+
for (const raw of value) {
|
|
87
|
+
const requirement = recordValue(raw)
|
|
88
|
+
const type = boundedString(requirement?.type)
|
|
89
|
+
const level = boundedString(requirement?.level)
|
|
90
|
+
if (type && level) requirements.push({type, level})
|
|
91
|
+
}
|
|
92
|
+
return requirements.slice(0, MAX_DIAGNOSTIC_LIST_ITEMS)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function diagnosticActionIds(value: unknown): string[] {
|
|
96
|
+
if (!Array.isArray(value)) return []
|
|
97
|
+
return value
|
|
98
|
+
.map((raw) => boundedString(typeof raw === "string" ? raw : recordValue(raw)?.id))
|
|
99
|
+
.filter((id): id is string => id !== undefined)
|
|
100
|
+
.sort()
|
|
101
|
+
.slice(0, MAX_DIAGNOSTIC_LIST_ITEMS)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Keep the routing/build fields from miniapp.json while excluding arbitrary
|
|
106
|
+
* free-form manifest content from reports. Descriptions, URLs, and unknown
|
|
107
|
+
* extension fields are intentionally omitted.
|
|
108
|
+
*/
|
|
109
|
+
export function buildMiniappManifestSnapshot(raw: unknown, fallback: ManifestFallback): MiniappManifestSnapshot {
|
|
110
|
+
const manifest = recordValue(raw)
|
|
111
|
+
const entry = recordValue(manifest?.entry)
|
|
112
|
+
const packageName = boundedString(manifest?.packageName) ?? boundedString(fallback.packageName) ?? "unknown"
|
|
113
|
+
const name = boundedString(manifest?.name) ?? boundedString(fallback.name)
|
|
114
|
+
const version = boundedString(manifest?.version) ?? boundedString(fallback.version)
|
|
115
|
+
const type = boundedString(manifest?.type) ?? boundedString(fallback.type)
|
|
116
|
+
const background = boundedString(entry?.background)
|
|
117
|
+
const ui = boundedString(entry?.ui)
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
packageName,
|
|
121
|
+
...(name ? {name} : {}),
|
|
122
|
+
...(version ? {version} : {}),
|
|
123
|
+
...(boundedString(manifest?.sdkVersion) ? {sdkVersion: boundedString(manifest?.sdkVersion)} : {}),
|
|
124
|
+
...(boundedString(manifest?.minHostVersion) ? {minHostVersion: boundedString(manifest?.minHostVersion)} : {}),
|
|
125
|
+
...(type ? {type} : {}),
|
|
126
|
+
...(background || ui ? {entry: {...(background ? {background} : {}), ...(ui ? {ui} : {})}} : {}),
|
|
127
|
+
permissions: diagnosticPermissions(manifest?.permissions),
|
|
128
|
+
hardwareRequirements: diagnosticHardwareRequirements(manifest?.hardwareRequirements),
|
|
129
|
+
actionIds: diagnosticActionIds(manifest?.actions ?? manifest?.actionIds),
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function executionMode(app: ClientApp): "built_in" | "dev_server" | "dev_snapshot" | "installed_bundle" {
|
|
134
|
+
if (app.offline) return "built_in"
|
|
135
|
+
if (app.isMiniappDev && app.devUrl && !app.version) return "dev_server"
|
|
136
|
+
if (app.isMiniappDev) return "dev_snapshot"
|
|
137
|
+
return "installed_bundle"
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function safeReleaseIdentity(value: unknown): Record<string, string> | undefined {
|
|
141
|
+
const identity = recordValue(value)
|
|
142
|
+
if (!identity) return undefined
|
|
143
|
+
const out: Record<string, string> = {}
|
|
144
|
+
for (const key of ["source", "releaseId", "bundleSha256", "channel"] as const) {
|
|
145
|
+
const field = boundedString(identity[key])
|
|
146
|
+
if (field) out[key] = field
|
|
147
|
+
}
|
|
148
|
+
return Object.keys(out).length > 0 ? out : undefined
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function buildMiniappDiagnosticContext(input: {
|
|
152
|
+
apps: ClientApp[]
|
|
153
|
+
foregroundedPackage: string | null
|
|
154
|
+
runtime: MiniappRuntimeDiagnosticSnapshot
|
|
155
|
+
getLastOpenedAtMs: (app: ClientApp) => number
|
|
156
|
+
getManifest: (app: ClientApp) => unknown
|
|
157
|
+
getReleaseIdentity: (app: ClientApp) => unknown
|
|
158
|
+
}): Record<string, unknown> {
|
|
159
|
+
const runtimeByPackage = new Map(input.runtime.connectedApps.map((app) => [app.packageName, app]))
|
|
160
|
+
const appSnapshots = input.apps.map((app) => {
|
|
161
|
+
const lastOpenedAtMs = input.getLastOpenedAtMs(app)
|
|
162
|
+
const packageName = boundedString(app.packageName) ?? "unknown"
|
|
163
|
+
return {
|
|
164
|
+
packageName,
|
|
165
|
+
name: boundedString(app.name) ?? packageName,
|
|
166
|
+
version: boundedString(app.version) ?? null,
|
|
167
|
+
executionMode: executionMode(app),
|
|
168
|
+
running: app.running,
|
|
169
|
+
foregrounded: app.packageName === input.foregroundedPackage,
|
|
170
|
+
loading: app.loading,
|
|
171
|
+
healthy: app.healthy,
|
|
172
|
+
hidden: app.hidden,
|
|
173
|
+
type: app.type,
|
|
174
|
+
offline: app.offline,
|
|
175
|
+
local: app.local,
|
|
176
|
+
lastOpenedAtMs: Number.isFinite(lastOpenedAtMs) ? lastOpenedAtMs : 0,
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
const recentlyInteracted = [...appSnapshots]
|
|
181
|
+
.filter((app) => app.lastOpenedAtMs > 0)
|
|
182
|
+
.sort((a, b) => b.lastOpenedAtMs - a.lastOpenedAtMs)
|
|
183
|
+
.slice(0, MAX_RECENT_MINIAPPS)
|
|
184
|
+
.map((app) => ({packageName: app.packageName, lastOpenedAtMs: app.lastOpenedAtMs}))
|
|
185
|
+
|
|
186
|
+
const runningMiniapps = input.apps
|
|
187
|
+
.filter((app) => app.running)
|
|
188
|
+
.map((app) => {
|
|
189
|
+
const runtime = runtimeByPackage.get(app.packageName)
|
|
190
|
+
const releaseIdentity = safeReleaseIdentity(input.getReleaseIdentity(app))
|
|
191
|
+
const packageName = boundedString(app.packageName) ?? "unknown"
|
|
192
|
+
const manifest = buildMiniappManifestSnapshot(input.getManifest(app) ?? runtime?.manifest, {
|
|
193
|
+
packageName,
|
|
194
|
+
name: app.name,
|
|
195
|
+
version: app.version,
|
|
196
|
+
type: app.type,
|
|
197
|
+
})
|
|
198
|
+
return {
|
|
199
|
+
packageName,
|
|
200
|
+
version: boundedString(app.version) ?? manifest.version ?? null,
|
|
201
|
+
executionMode: executionMode(app),
|
|
202
|
+
foregrounded: app.packageName === input.foregroundedPackage,
|
|
203
|
+
manifest,
|
|
204
|
+
...(releaseIdentity ? {releaseIdentity} : {}),
|
|
205
|
+
runtime: runtime
|
|
206
|
+
? {
|
|
207
|
+
handshakeComplete: runtime.handshakeComplete,
|
|
208
|
+
subscriptions: runtime.subscriptions,
|
|
209
|
+
lastMessageAgeMs: runtime.lastMessageAgeMs,
|
|
210
|
+
requestedLocationRate: runtime.requestedLocationRate,
|
|
211
|
+
hasActiveSpeakerStream: runtime.hasActiveSpeakerStream,
|
|
212
|
+
}
|
|
213
|
+
: null,
|
|
214
|
+
}
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
apps: appSnapshots,
|
|
219
|
+
installed: appSnapshots.map((app) => app.packageName),
|
|
220
|
+
running: runningMiniapps.map((app) => app.packageName),
|
|
221
|
+
foregroundedPackage: boundedString(input.foregroundedPackage) ?? null,
|
|
222
|
+
mostRecentlyInteractedPackage: recentlyInteracted[0]?.packageName ?? null,
|
|
223
|
+
recentlyInteracted,
|
|
224
|
+
runningMiniapps,
|
|
225
|
+
}
|
|
226
|
+
}
|
|
@@ -1,6 +1,28 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
// BgTimer — background-safe timers for the engine.
|
|
2
|
+
//
|
|
3
|
+
// Everything timing-critical in the engine runs through this class: the
|
|
4
|
+
// miniapp liveness watchdog, display durationMs expiries, boot windows,
|
|
5
|
+
// auth-token refresh. On Android, React Native PAUSES plain JS timers while
|
|
6
|
+
// the app is backgrounded, so those features only keep working in the
|
|
7
|
+
// background when the native implementation (react-native-nitro-bg-timer)
|
|
8
|
+
// is active.
|
|
9
|
+
//
|
|
10
|
+
// Who actually gets native timers:
|
|
11
|
+
// - Android, dev AND release: always attempted — dev must match production
|
|
12
|
+
// background behavior. There is no opt-in/opt-out env var. If the native
|
|
13
|
+
// module is unavailable (a dev client whose binary predates it, or a
|
|
14
|
+
// broken release binary), the failure is LOUD: a console.error with the
|
|
15
|
+
// remediation, plus a blocking dev alert. The JS-timer fallback still
|
|
16
|
+
// engages so foreground behavior keeps working, but backgrounded
|
|
17
|
+
// behavior is broken until the native rebuild — the error says exactly
|
|
18
|
+
// that. (A silent dev fallback previously cost a full debugging day of
|
|
19
|
+
// phantom "zombie miniapp" bugs; see OS-1790.)
|
|
20
|
+
// - iOS: never, dev or prod — the package is disabled pending
|
|
21
|
+
// https://github.com/tconns/react-native-nitro-bg-timer/issues/2. iOS
|
|
22
|
+
// suspends the whole process in background anyway, so background timing
|
|
23
|
+
// there is handled by native per-device queues, not JS timers.
|
|
24
|
+
|
|
25
|
+
import {Alert, Platform} from "react-native"
|
|
4
26
|
|
|
5
27
|
type NitroTimerApi = {
|
|
6
28
|
setInterval: (callback: () => void, delay: number) => number
|
|
@@ -11,14 +33,36 @@ type NitroTimerApi = {
|
|
|
11
33
|
|
|
12
34
|
let nitroTimer: NitroTimerApi | null | undefined
|
|
13
35
|
let nitroDisabled = false
|
|
14
|
-
let
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
36
|
+
let failedLoud = false
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The native background timer is unavailable — say so ONCE, loudly, with the
|
|
40
|
+
* remediation. The JS-timer fallback keeps foreground behavior working, but
|
|
41
|
+
* everything timing-driven freezes while the app is backgrounded, which
|
|
42
|
+
* presents as phantom miniapp bugs (frozen captions, missed wake words,
|
|
43
|
+
* watchdog kills on resume). A quiet warning here has already cost a full
|
|
44
|
+
* debugging day; never downgrade this to console.warn.
|
|
45
|
+
*/
|
|
46
|
+
function failUnavailable(reason: string) {
|
|
47
|
+
if (failedLoud) {
|
|
18
48
|
return
|
|
19
49
|
}
|
|
20
|
-
|
|
21
|
-
|
|
50
|
+
failedLoud = true
|
|
51
|
+
const message =
|
|
52
|
+
`BgTimer: native background timer unavailable (${reason}). ` +
|
|
53
|
+
"Backgrounded behavior IS BROKEN until you rebuild the Android app: run `bun android`. " +
|
|
54
|
+
"Engine timers and local miniapps (captions, wake words) freeze whenever the app is not foregrounded."
|
|
55
|
+
console.error(message)
|
|
56
|
+
if (__DEV__) {
|
|
57
|
+
// A stale dev client may ALSO show the library's own require-time red box
|
|
58
|
+
// (bridgeless LogBox surfaces it even though we catch below) — this alert
|
|
59
|
+
// is the actionable one: it names the fix.
|
|
60
|
+
try {
|
|
61
|
+
Alert.alert("Background timers unavailable", message)
|
|
62
|
+
} catch {
|
|
63
|
+
/* Alert not available (tests / headless) — the console.error stands. */
|
|
64
|
+
}
|
|
65
|
+
}
|
|
22
66
|
}
|
|
23
67
|
|
|
24
68
|
function getNitroTimer(): NitroTimerApi | null {
|
|
@@ -26,15 +70,6 @@ function getNitroTimer(): NitroTimerApi | null {
|
|
|
26
70
|
return null
|
|
27
71
|
}
|
|
28
72
|
|
|
29
|
-
// react-native-nitro-bg-timer calls createHybridObject() at require() time. On dev
|
|
30
|
-
// builds with a stale native binary that throws NPE, React Native still surfaces a
|
|
31
|
-
// red LogBox error even when the exception is caught. Skip nitro in __DEV__ unless
|
|
32
|
-
// explicitly enabled after a native rebuild (bun android).
|
|
33
|
-
if (__DEV__ && process.env.EXPO_PUBLIC_USE_NITRO_BG_TIMER !== "true") {
|
|
34
|
-
warnUnavailable("nitro bg-timer disabled in dev (set EXPO_PUBLIC_USE_NITRO_BG_TIMER=true after native rebuild)")
|
|
35
|
-
return null
|
|
36
|
-
}
|
|
37
|
-
|
|
38
73
|
if (nitroTimer !== undefined) {
|
|
39
74
|
return nitroTimer
|
|
40
75
|
}
|
|
@@ -43,7 +78,7 @@ function getNitroTimer(): NitroTimerApi | null {
|
|
|
43
78
|
const {isRuntimeAlive} = require("react-native-nitro-modules") as {isRuntimeAlive?: () => boolean}
|
|
44
79
|
if (typeof isRuntimeAlive === "function" && !isRuntimeAlive()) {
|
|
45
80
|
nitroTimer = null
|
|
46
|
-
|
|
81
|
+
failUnavailable("nitro runtime is not alive")
|
|
47
82
|
return nitroTimer
|
|
48
83
|
}
|
|
49
84
|
|
|
@@ -62,7 +97,7 @@ function getNitroTimer(): NitroTimerApi | null {
|
|
|
62
97
|
}
|
|
63
98
|
|
|
64
99
|
if (!nitroTimer) {
|
|
65
|
-
|
|
100
|
+
failUnavailable("react-native-nitro-bg-timer failed to load")
|
|
66
101
|
}
|
|
67
102
|
return nitroTimer
|
|
68
103
|
}
|
|
@@ -77,7 +112,8 @@ function withNitro<T>(run: (api: NitroTimerApi) => T, fallback: () => T): T {
|
|
|
77
112
|
} catch (error) {
|
|
78
113
|
nitroDisabled = true
|
|
79
114
|
nitroTimer = null
|
|
80
|
-
|
|
115
|
+
failedLoud = false // a mid-session failure is new information — re-announce
|
|
116
|
+
failUnavailable(`nitro timer call threw: ${String(error)}`)
|
|
81
117
|
return fallback()
|
|
82
118
|
}
|
|
83
119
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"asgOtaVersionUrl.d.ts","sourceRoot":"","sources":["../../src/services/asgOtaVersionUrl.ts"],"names":[],"mappings":"AAsBA,wBAAgB,mBAAmB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAkB1G"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"asgOtaVersionUrl.js","sourceRoot":"","sources":["../../src/services/asgOtaVersionUrl.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,QAAQ,EAAE,gBAAgB,EAAC,MAAM,oBAAoB,CAAA;AAE7D,MAAM,oBAAoB,GAAG,oDAAoD,CAAA;AAEjF,SAAS,wBAAwB,CAAC,kBAAkC;IAClE,MAAM,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,kBAAkB,IAAI,EAAE,EAAE,EAAE,CAAC,CAAA;IACjE,4EAA4E;IAC5E,kCAAkC;IAClC,OAAO,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,WAAW,GAAG,EAAE,CAAA;AACzD,CAAC;AAED,SAAS,2BAA2B;IAClC,sEAAsE;IACtE,4DAA4D;IAC5D,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACrE,OAAO,IAAI,CAAA;IACb,CAAC;IACD,MAAM,KAAK,GAAG,gBAAgB,CAAC,QAAQ,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,eAAe,CAAC,GAAG,CAAC,CAAA;IAClF,MAAM,OAAO,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;IAC7D,OAAO,OAAO,IAAI,IAAI,CAAA;AACxB,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,UAA0B,EAAE,kBAAkC;IAChG,MAAM,SAAS,GAAG,UAAU,EAAE,IAAI,EAAE,CAAA;IACpC,IAAI,wBAAwB,CAAC,kBAAkB,CAAC,EAAE,CAAC;QACjD,yEAAyE;QACzE,6EAA6E;QAC7E,OAAO,SAAS,IAAI,oBAAoB,CAAA;IAC1C,CAAC;IAED,MAAM,cAAc,GAAG,2BAA2B,EAAE,CAAA;IACpD,IAAI,cAAc,EAAE,CAAC;QACnB,OAAO,cAAc,CAAA;IACvB,CAAC;IAED,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,+BAA+B,EAAE,IAAI,EAAE,CAAA;IAClE,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,MAAM,CAAA;IACf,CAAC;IACD,OAAO,SAAS,IAAI,oBAAoB,CAAA;AAC1C,CAAC","sourcesContent":["import {SETTINGS, useSettingsStore} from \"../stores/settings\"\n\nconst OTA_VERSION_URL_PROD = \"https://ota.mentraglass.com/prod_live_version.json\"\n\nfunction isLegacyAsgOtaStartBuild(glassesBuildNumber?: string | null): boolean {\n const buildNumber = Number.parseInt(glassesBuildNumber ?? \"\", 10)\n // ASG builds before 39 ignore ota_start.ota_version_url, so compare against\n // the URL they will actually use.\n return Number.isFinite(buildNumber) && buildNumber < 39\n}\n\nfunction getOtaVersionUrlDevOverride(): string | null {\n // Super mode only: a wrong OTA manifest can brick glasses, so a saved\n // override is inert unless super mode is currently enabled.\n if (!useSettingsStore.getState().getSetting(SETTINGS.super_mode.key)) {\n return null\n }\n const value = useSettingsStore.getState().getSetting(SETTINGS.ota_version_url.key)\n const trimmed = typeof value === \"string\" ? value.trim() : \"\"\n return trimmed || null\n}\n\nexport function getAsgOtaVersionUrl(glassesUrl?: string | null, glassesBuildNumber?: string | null): string {\n const deviceUrl = glassesUrl?.trim()\n if (isLegacyAsgOtaStartBuild(glassesBuildNumber)) {\n // Legacy glasses ignore ota_start.ota_version_url and install from their\n // compiled default, so the developer override does not apply to them either.\n return deviceUrl || OTA_VERSION_URL_PROD\n }\n\n const devOverrideUrl = getOtaVersionUrlDevOverride()\n if (devOverrideUrl) {\n return devOverrideUrl\n }\n\n const envUrl = process.env.EXPO_PUBLIC_ASG_OTA_VERSION_URL?.trim()\n if (envUrl) {\n return envUrl\n }\n return deviceUrl || OTA_VERSION_URL_PROD\n}\n"]}
|