@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
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* This runtime handles request dispatch, stream fan-out, and lifecycle
|
|
9
9
|
* management (connect/disconnect/ping).
|
|
10
10
|
*/
|
|
11
|
-
import { Linking } from "react-native";
|
|
11
|
+
import { AppState, Linking } from "react-native";
|
|
12
12
|
import Share from "react-native-share";
|
|
13
13
|
import * as Battery from "expo-battery";
|
|
14
14
|
import * as Clipboard from "expo-clipboard";
|
|
@@ -38,16 +38,24 @@ import { phoneStreamCoordinator } from "./PhoneStreamCoordinator";
|
|
|
38
38
|
import { phoneVideoCoordinator } from "./PhoneVideoCoordinator";
|
|
39
39
|
import { runSentenceTtsPipeline } from "./SentenceTtsPipeline";
|
|
40
40
|
import { summarizeTranscriptionRoutes, transcriptionDeliveryRoute } from "./TranscriptionRouting";
|
|
41
|
+
import { prepareTtsSentences } from "./TtsTextSanitizer";
|
|
42
|
+
import { handleWifiAdbRequest } from "./WifiAdbRequest";
|
|
41
43
|
import { cloudClientService } from "./CloudClientService";
|
|
42
44
|
import { miniappLauncher } from "./MiniappLauncher";
|
|
43
45
|
import { ISLAND_SETTINGS_KEYS, } from "../runtime/config";
|
|
44
46
|
import { getAnalytics, getUiSeams } from "../runtime/bootstrap";
|
|
47
|
+
import { invokeScanQrSeam } from "../runtime/scanQrSeam";
|
|
45
48
|
import { normalizeStreamAudioConfig, normalizeStreamVideoConfig } from "../runtime/streamConfig";
|
|
49
|
+
import { toLanguageHint } from "@mentra/cloud-protocol/languages";
|
|
50
|
+
import { buildMiniappManifestSnapshot } from "../utils/miniappDiagnostics";
|
|
46
51
|
import ttsModelManager from "./TTSModelManager";
|
|
47
52
|
import { NavigationHandlers } from "./NavigationHandlers";
|
|
48
53
|
import { useAppStatusStore } from "../stores/apps";
|
|
49
54
|
import { getDevAppAttestation, getDevAppSourcePackage } from "./AppRegistry";
|
|
55
|
+
import { resolveForegroundLocationPermission } from "./ForegroundLocationPermission";
|
|
56
|
+
import { advanceMiniappPingLiveness } from "./MiniappLiveness";
|
|
50
57
|
import { listPhoneCalendarEvents, PhoneCalendarError } from "./PhoneCalendarService";
|
|
58
|
+
import { LocalMiniappStorage } from "./LocalMiniappStorage";
|
|
51
59
|
/**
|
|
52
60
|
* Strictness ordering for `location_stream` rate values. Higher index =
|
|
53
61
|
* higher accuracy and higher power cost. The aggregate tier applied to
|
|
@@ -80,6 +88,14 @@ function withTimeout(promise, timeoutMs) {
|
|
|
80
88
|
});
|
|
81
89
|
}
|
|
82
90
|
const LOG_TAG = "LOCAL_MINIAPP";
|
|
91
|
+
const DIAGNOSTIC_MAX_LIST_ITEMS = 100;
|
|
92
|
+
const DIAGNOSTIC_MAX_STRING_LENGTH = 512;
|
|
93
|
+
function diagnosticStringList(values) {
|
|
94
|
+
return [...values]
|
|
95
|
+
.map((value) => value.slice(0, DIAGNOSTIC_MAX_STRING_LENGTH))
|
|
96
|
+
.sort()
|
|
97
|
+
.slice(0, DIAGNOSTIC_MAX_LIST_ITEMS);
|
|
98
|
+
}
|
|
83
99
|
const SYSTEM_MINIAPP_PACKAGES = new Set([
|
|
84
100
|
"com.mentra.camera",
|
|
85
101
|
"com.mentra.gallery",
|
|
@@ -115,9 +131,27 @@ const RGB_LED_ACTIONS = new Set(["on", "off"]);
|
|
|
115
131
|
const RGB_LED_COLORS = new Set(["red", "green", "blue", "orange", "white"]);
|
|
116
132
|
const CAMERA_FOV_MIN = 62;
|
|
117
133
|
const CAMERA_FOV_MAX = 118;
|
|
118
|
-
const CAMERA_FOV_DEFAULT =
|
|
119
|
-
const CAMERA_FOV_PRESETS = { narrow: 82, standard:
|
|
134
|
+
const CAMERA_FOV_DEFAULT = 118;
|
|
135
|
+
const CAMERA_FOV_PRESETS = { narrow: 82, standard: 102, wide: 118 };
|
|
120
136
|
const CAMERA_ROI_POSITION_BY_NAME = { center: "center", bottom: "bottom", top: "top" };
|
|
137
|
+
const localMiniappStorageBackend = {
|
|
138
|
+
get(key) {
|
|
139
|
+
const result = mmkvStorage.load(key);
|
|
140
|
+
return result.is_ok() ? result.value : null;
|
|
141
|
+
},
|
|
142
|
+
has(key) {
|
|
143
|
+
return mmkvStorage.load(key).is_ok();
|
|
144
|
+
},
|
|
145
|
+
set(key, value) {
|
|
146
|
+
mmkvStorage.save(key, value);
|
|
147
|
+
},
|
|
148
|
+
remove(key) {
|
|
149
|
+
mmkvStorage.remove(key);
|
|
150
|
+
},
|
|
151
|
+
keys() {
|
|
152
|
+
return mmkvStorage.getAllKeys();
|
|
153
|
+
},
|
|
154
|
+
};
|
|
121
155
|
// =============================================================================
|
|
122
156
|
// Declared-permission record helper (for CONNECT_ACK / PERMISSIONS_UPDATE)
|
|
123
157
|
// =============================================================================
|
|
@@ -234,10 +268,19 @@ class LocalMiniappRuntime {
|
|
|
234
268
|
connectWaiters = new Map();
|
|
235
269
|
/** Ref-counted stream subscriptions: stream → set of packageNames. */
|
|
236
270
|
streamSubscribers = new Map();
|
|
271
|
+
/** Host observers for the Mentra Live button-capture policy. */
|
|
272
|
+
buttonPressSubscriberListeners = new Set();
|
|
237
273
|
/** Guards one-time wiring of the cloud transcript/translation fan-out. */
|
|
238
274
|
cloudResultsWired = false;
|
|
239
275
|
cloudStatusWired = false;
|
|
240
276
|
cloudAudioSubscriptionSync = new CloudAudioSubscriptionSync();
|
|
277
|
+
/**
|
|
278
|
+
* Per-miniapp language hints from TRANSCRIPTION_CONFIG (issue 021 WP3).
|
|
279
|
+
* Bare ISO 639-1 codes, already registry-validated by the SDK and
|
|
280
|
+
* re-normalized here (defense in depth). Merged (union) into the auto-mode
|
|
281
|
+
* cloud transcription subscription in buildCloudAudioSubscriptions.
|
|
282
|
+
*/
|
|
283
|
+
transcriptionHintsByApp = new Map();
|
|
241
284
|
/** Ping interval handle. */
|
|
242
285
|
pingIntervalId = null;
|
|
243
286
|
foregroundProbeTimers = new Map();
|
|
@@ -259,6 +302,88 @@ class LocalMiniappRuntime {
|
|
|
259
302
|
localSecret = `miniapp_${Date.now()}_${Math.random().toString(36).slice(2, 14)}`;
|
|
260
303
|
usedTokens = new Set();
|
|
261
304
|
constructor() { }
|
|
305
|
+
/** Snapshot of miniapps actively subscribed to hardware button events. */
|
|
306
|
+
getButtonPressSubscribers() {
|
|
307
|
+
return [...(this.streamSubscribers.get(MiniappStreamType.BUTTON_PRESS) ?? [])].sort();
|
|
308
|
+
}
|
|
309
|
+
/** Observe active hardware-button subscriptions. */
|
|
310
|
+
onButtonPressSubscribersChanged(listener) {
|
|
311
|
+
this.buttonPressSubscriberListeners.add(listener);
|
|
312
|
+
return () => this.buttonPressSubscriberListeners.delete(listener);
|
|
313
|
+
}
|
|
314
|
+
/** Report-safe live miniapp/subscription/resource snapshot. */
|
|
315
|
+
getDiagnosticSnapshot(nowMs = Date.now()) {
|
|
316
|
+
const connectedApps = [...this.connectedApps.entries()]
|
|
317
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
318
|
+
.slice(0, DIAGNOSTIC_MAX_LIST_ITEMS)
|
|
319
|
+
.map(([packageName, app]) => ({
|
|
320
|
+
packageName: packageName.slice(0, DIAGNOSTIC_MAX_STRING_LENGTH),
|
|
321
|
+
handshakeComplete: this.handshookApps.has(packageName),
|
|
322
|
+
subscriptions: diagnosticStringList(app.subscriptions),
|
|
323
|
+
lastMessageAgeMs: Math.max(0, nowMs - app.lastPongAt),
|
|
324
|
+
requestedLocationRate: app.requestedLocationRate,
|
|
325
|
+
hasActiveSpeakerStream: app.activeSpeakerStreamId !== undefined,
|
|
326
|
+
...(app.installedManifest
|
|
327
|
+
? {
|
|
328
|
+
manifest: buildMiniappManifestSnapshot(app.installedManifest, {
|
|
329
|
+
packageName,
|
|
330
|
+
name: app.installedManifest.name,
|
|
331
|
+
version: app.installedManifest.version,
|
|
332
|
+
type: app.installedManifest.type,
|
|
333
|
+
}),
|
|
334
|
+
}
|
|
335
|
+
: {}),
|
|
336
|
+
}));
|
|
337
|
+
const subscriptionsByStream = Object.fromEntries([...this.streamSubscribers.entries()]
|
|
338
|
+
.filter(([, packageNames]) => packageNames.size > 0)
|
|
339
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
340
|
+
.slice(0, DIAGNOSTIC_MAX_LIST_ITEMS)
|
|
341
|
+
.map(([stream, packageNames]) => [
|
|
342
|
+
stream.slice(0, DIAGNOSTIC_MAX_STRING_LENGTH),
|
|
343
|
+
diagnosticStringList(packageNames),
|
|
344
|
+
]));
|
|
345
|
+
return {
|
|
346
|
+
connectedApps,
|
|
347
|
+
subscriptionsByStream,
|
|
348
|
+
resources: {
|
|
349
|
+
display: localDisplayManager.getDiagnosticSnapshot(),
|
|
350
|
+
camera: phonePhotoCoordinator.getDiagnosticSnapshot(),
|
|
351
|
+
video: phoneVideoCoordinator.getDiagnosticSnapshot(),
|
|
352
|
+
stream: phoneStreamCoordinator.getDiagnosticSnapshot(),
|
|
353
|
+
cameraFov: phoneCameraFovCoordinator.getDiagnosticSnapshot(),
|
|
354
|
+
},
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
replaceStreamSubscribers(packageName, previousStreams, nextStreams) {
|
|
358
|
+
const previousButtonSubscribers = this.getButtonPressSubscribers().join("\u0000");
|
|
359
|
+
for (const stream of previousStreams) {
|
|
360
|
+
const subscribers = this.streamSubscribers.get(stream);
|
|
361
|
+
if (!subscribers)
|
|
362
|
+
continue;
|
|
363
|
+
subscribers.delete(packageName);
|
|
364
|
+
if (subscribers.size === 0)
|
|
365
|
+
this.streamSubscribers.delete(stream);
|
|
366
|
+
}
|
|
367
|
+
for (const stream of nextStreams) {
|
|
368
|
+
let subscribers = this.streamSubscribers.get(stream);
|
|
369
|
+
if (!subscribers) {
|
|
370
|
+
subscribers = new Set();
|
|
371
|
+
this.streamSubscribers.set(stream, subscribers);
|
|
372
|
+
}
|
|
373
|
+
subscribers.add(packageName);
|
|
374
|
+
}
|
|
375
|
+
const nextButtonSubscribers = this.getButtonPressSubscribers();
|
|
376
|
+
if (previousButtonSubscribers !== nextButtonSubscribers.join("\u0000")) {
|
|
377
|
+
for (const listener of this.buttonPressSubscriberListeners) {
|
|
378
|
+
try {
|
|
379
|
+
listener(nextButtonSubscribers);
|
|
380
|
+
}
|
|
381
|
+
catch (error) {
|
|
382
|
+
console.warn(`${LOG_TAG}: button press subscriber listener threw:`, error);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
262
387
|
/**
|
|
263
388
|
* Generate an HMAC-signed local session token for browser fallback auth.
|
|
264
389
|
* Token format: base64(JSON({userId, packageName, exp})).base64(HMAC-SHA256(payload, secret))
|
|
@@ -466,14 +591,7 @@ class LocalMiniappRuntime {
|
|
|
466
591
|
BgTimer.clearTimeout(existing.authRetryTimerId);
|
|
467
592
|
existing.authRetryTimerId = null;
|
|
468
593
|
}
|
|
469
|
-
|
|
470
|
-
const subs = this.streamSubscribers.get(stream);
|
|
471
|
-
if (subs) {
|
|
472
|
-
subs.delete(packageName);
|
|
473
|
-
if (subs.size === 0)
|
|
474
|
-
this.streamSubscribers.delete(stream);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
594
|
+
this.replaceStreamSubscribers(packageName, existing.subscriptions, []);
|
|
477
595
|
this.recomputeMicRequirements();
|
|
478
596
|
this.updateCloudSubscriptions();
|
|
479
597
|
}
|
|
@@ -483,6 +601,7 @@ class LocalMiniappRuntime {
|
|
|
483
601
|
cloudTranscriptionStreams: new Set(),
|
|
484
602
|
sendMessage: sendFn,
|
|
485
603
|
lastPongAt: Date.now(),
|
|
604
|
+
unansweredPingRounds: 0,
|
|
486
605
|
installedManifest,
|
|
487
606
|
authRefreshTimerId: null,
|
|
488
607
|
authRetryTimerId: null,
|
|
@@ -602,6 +721,7 @@ class LocalMiniappRuntime {
|
|
|
602
721
|
}
|
|
603
722
|
unregisterApp(packageName) {
|
|
604
723
|
console.log(`${LOG_TAG}: unregisterApp(${packageName})`);
|
|
724
|
+
const releasedMicGateOverride = micStateCoordinator.clearMiniappGateOverrides(packageName);
|
|
605
725
|
this.clearForegroundProbe(packageName);
|
|
606
726
|
this.clearMiniappAuthRefresh(packageName);
|
|
607
727
|
this.clearMiniappAuthDeliveryRetry(packageName);
|
|
@@ -610,6 +730,9 @@ class LocalMiniappRuntime {
|
|
|
610
730
|
// the early-return so it runs even if the connectedApps entry is already gone.
|
|
611
731
|
this.handshookApps.delete(packageName);
|
|
612
732
|
this.flushConnectWaiters(packageName, new Error(`${packageName} unregistered before connect`));
|
|
733
|
+
// Drop this app's transcription hints; the cloud subscription rebuild
|
|
734
|
+
// below (via replaceStreamSubscribers) re-derives the hint union without it.
|
|
735
|
+
this.transcriptionHintsByApp.delete(packageName);
|
|
613
736
|
// Warm-up is a request-owned camera lease. Cancel it even if the app record was already
|
|
614
737
|
// removed so a close racing the camera-open promise cannot leave the sensor running.
|
|
615
738
|
// Keep camera teardown ordered: restoring FOV can restart the HAL, so the warm-up lease must
|
|
@@ -625,18 +748,16 @@ class LocalMiniappRuntime {
|
|
|
625
748
|
console.warn(`${LOG_TAG}: failed to release camera FOV override for ${packageName} on unregister`, error);
|
|
626
749
|
});
|
|
627
750
|
const app = this.connectedApps.get(packageName);
|
|
628
|
-
if (!app)
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
if (subs) {
|
|
634
|
-
subs.delete(packageName);
|
|
635
|
-
if (subs.size === 0) {
|
|
636
|
-
this.streamSubscribers.delete(stream);
|
|
637
|
-
}
|
|
751
|
+
if (!app) {
|
|
752
|
+
if (releasedMicGateOverride) {
|
|
753
|
+
void micStateCoordinator.syncEffectiveGatePolicy(this.getConfiguredMicGates()).catch((error) => {
|
|
754
|
+
console.warn(`${LOG_TAG}: failed to restore mic gate policy for ${packageName}`, error);
|
|
755
|
+
});
|
|
638
756
|
}
|
|
757
|
+
return;
|
|
639
758
|
}
|
|
759
|
+
// Remove from all stream subscriber sets
|
|
760
|
+
this.replaceStreamSubscribers(packageName, app.subscriptions, []);
|
|
640
761
|
// Reset per-session warning dedup so a relaunch surfaces issues again.
|
|
641
762
|
resetPermissionWarnings(packageName);
|
|
642
763
|
// Cancel lookahead/in-flight synthesis before stopping the current file so
|
|
@@ -691,6 +812,11 @@ class LocalMiniappRuntime {
|
|
|
691
812
|
localDisplayManager.onUnmount(packageName);
|
|
692
813
|
this.connectedApps.delete(packageName);
|
|
693
814
|
this.recomputeMicRequirements();
|
|
815
|
+
if (releasedMicGateOverride) {
|
|
816
|
+
void micStateCoordinator.syncEffectiveGatePolicy(this.getConfiguredMicGates()).catch((error) => {
|
|
817
|
+
console.warn(`${LOG_TAG}: failed to restore mic gate policy for ${packageName}`, error);
|
|
818
|
+
});
|
|
819
|
+
}
|
|
694
820
|
this.updateCloudSubscriptions();
|
|
695
821
|
if (this.connectedApps.size === 0) {
|
|
696
822
|
this.stopPingLoop();
|
|
@@ -856,9 +982,18 @@ class LocalMiniappRuntime {
|
|
|
856
982
|
case MiniappRequestType.CAMERA_FOV:
|
|
857
983
|
void this.handleCameraFov(packageName, payload, requestId);
|
|
858
984
|
break;
|
|
985
|
+
case MiniappRequestType.SET_WIFI_ADB_STATE:
|
|
986
|
+
void this.handleSetWifiAdbState(packageName, payload, requestId);
|
|
987
|
+
break;
|
|
859
988
|
case MiniappRequestType.IMU_SET_ENABLED:
|
|
860
989
|
this.handleImuSetEnabled(packageName, payload, requestId);
|
|
861
990
|
break;
|
|
991
|
+
case MiniappRequestType.MIC_SET_VAD_ENABLED:
|
|
992
|
+
void this.handleMicSetVadEnabled(packageName, payload, requestId);
|
|
993
|
+
break;
|
|
994
|
+
case MiniappRequestType.MIC_SET_LOUDNESS_GATE_ENABLED:
|
|
995
|
+
void this.handleMicSetLoudnessGateEnabled(packageName, payload, requestId);
|
|
996
|
+
break;
|
|
862
997
|
case MiniappRequestType.PING:
|
|
863
998
|
// SDK should handle this itself; reply PONG just in case
|
|
864
999
|
this.sendToMiniapp(packageName, { type: MiniappResponseType.PONG }, requestId);
|
|
@@ -880,51 +1015,54 @@ class LocalMiniappRuntime {
|
|
|
880
1015
|
case MiniappRequestType.DOWNLOAD:
|
|
881
1016
|
this.handleDownload(packageName, payload, requestId);
|
|
882
1017
|
break;
|
|
1018
|
+
case MiniappRequestType.SCAN_QR:
|
|
1019
|
+
void this.handleScanQr(packageName, payload, requestId);
|
|
1020
|
+
break;
|
|
883
1021
|
// Persistent binary blob storage (session.blob)
|
|
884
1022
|
case MiniappRequestType.BLOB_CREATE:
|
|
885
|
-
this.blobStore.handleCreate(packageName, payload, requestId);
|
|
1023
|
+
this.enqueueBlobRequest(packageName, requestId, () => this.blobStore.handleCreate(packageName, payload, requestId));
|
|
886
1024
|
break;
|
|
887
1025
|
case MiniappRequestType.BLOB_WRITE:
|
|
888
|
-
this.blobStore.handleWrite(packageName, payload, requestId);
|
|
1026
|
+
this.enqueueBlobRequest(packageName, requestId, () => this.blobStore.handleWrite(packageName, payload, requestId));
|
|
889
1027
|
break;
|
|
890
1028
|
case MiniappRequestType.BLOB_COMMIT:
|
|
891
|
-
this.blobStore.handleCommit(packageName, payload, requestId);
|
|
1029
|
+
this.enqueueBlobRequest(packageName, requestId, () => this.blobStore.handleCommit(packageName, payload, requestId));
|
|
892
1030
|
break;
|
|
893
1031
|
case MiniappRequestType.BLOB_ABORT:
|
|
894
|
-
this.blobStore.handleAbort(packageName, payload, requestId);
|
|
1032
|
+
this.enqueueBlobRequest(packageName, requestId, () => this.blobStore.handleAbort(packageName, payload, requestId));
|
|
895
1033
|
break;
|
|
896
1034
|
case MiniappRequestType.BLOB_SET_FROM_URL:
|
|
897
|
-
|
|
1035
|
+
this.enqueueBlobRequest(packageName, requestId, () => this.blobStore.handleSetFromUrl(packageName, payload, requestId));
|
|
898
1036
|
break;
|
|
899
1037
|
case MiniappRequestType.BLOB_IMPORT:
|
|
900
|
-
|
|
1038
|
+
this.enqueueBlobRequest(packageName, requestId, () => this.blobStore.handleImport(packageName, payload, requestId));
|
|
901
1039
|
break;
|
|
902
1040
|
case MiniappRequestType.BLOB_GET:
|
|
903
|
-
this.blobStore.handleGet(packageName, payload, requestId);
|
|
1041
|
+
this.enqueueBlobRequest(packageName, requestId, () => this.blobStore.handleGet(packageName, payload, requestId));
|
|
904
1042
|
break;
|
|
905
1043
|
case MiniappRequestType.BLOB_LIST:
|
|
906
|
-
this.blobStore.handleList(packageName, payload, requestId);
|
|
1044
|
+
this.enqueueBlobRequest(packageName, requestId, () => this.blobStore.handleList(packageName, payload, requestId));
|
|
907
1045
|
break;
|
|
908
1046
|
case MiniappRequestType.BLOB_USAGE:
|
|
909
|
-
this.blobStore.handleUsage(packageName, payload, requestId);
|
|
1047
|
+
this.enqueueBlobRequest(packageName, requestId, () => this.blobStore.handleUsage(packageName, payload, requestId));
|
|
910
1048
|
break;
|
|
911
1049
|
case MiniappRequestType.BLOB_DELETE:
|
|
912
|
-
this.blobStore.handleDelete(packageName, payload, requestId);
|
|
1050
|
+
this.enqueueBlobRequest(packageName, requestId, () => this.blobStore.handleDelete(packageName, payload, requestId));
|
|
913
1051
|
break;
|
|
914
1052
|
case MiniappRequestType.BLOB_CLEAR:
|
|
915
|
-
this.blobStore.handleClear(packageName, payload, requestId);
|
|
1053
|
+
this.enqueueBlobRequest(packageName, requestId, () => this.blobStore.handleClear(packageName, payload, requestId));
|
|
916
1054
|
break;
|
|
917
1055
|
case MiniappRequestType.BLOB_OPEN_READ:
|
|
918
|
-
this.blobStore.handleOpenRead(packageName, payload, requestId);
|
|
1056
|
+
this.enqueueBlobRequest(packageName, requestId, () => this.blobStore.handleOpenRead(packageName, payload, requestId));
|
|
919
1057
|
break;
|
|
920
1058
|
case MiniappRequestType.BLOB_READ:
|
|
921
|
-
this.blobStore.handleRead(packageName, payload, requestId);
|
|
1059
|
+
this.enqueueBlobRequest(packageName, requestId, () => this.blobStore.handleRead(packageName, payload, requestId));
|
|
922
1060
|
break;
|
|
923
1061
|
case MiniappRequestType.BLOB_CLOSE_READ:
|
|
924
|
-
this.blobStore.handleCloseRead(packageName, payload, requestId);
|
|
1062
|
+
this.enqueueBlobRequest(packageName, requestId, () => this.blobStore.handleCloseRead(packageName, payload, requestId));
|
|
925
1063
|
break;
|
|
926
1064
|
case MiniappRequestType.BLOB_SHARE:
|
|
927
|
-
|
|
1065
|
+
this.enqueueBlobRequest(packageName, requestId, () => this.blobStore.handleShare(packageName, payload, requestId));
|
|
928
1066
|
break;
|
|
929
1067
|
// Cloud-coordinated features
|
|
930
1068
|
case MiniappRequestType.PHOTO:
|
|
@@ -977,8 +1115,19 @@ class LocalMiniappRuntime {
|
|
|
977
1115
|
message: "Dashboard API is deferred in v1",
|
|
978
1116
|
});
|
|
979
1117
|
break;
|
|
1118
|
+
case MiniappRequestType.TRANSCRIPTION_CONFIG:
|
|
1119
|
+
this.handleTranscriptionConfig(packageName, payload, requestId);
|
|
1120
|
+
break;
|
|
980
1121
|
default:
|
|
1122
|
+
// NACK instead of silent drop (issue 021 S3): an SDK that sends a
|
|
1123
|
+
// request this runtime doesn't implement must get a rejected promise,
|
|
1124
|
+
// not a permanently-pending silent gap. One-shots (no requestId) can
|
|
1125
|
+
// only be logged.
|
|
981
1126
|
console.warn(`${LOG_TAG}: Unknown request type from ${packageName}: ${requestType}`);
|
|
1127
|
+
this.sendResult(packageName, requestId, false, undefined, {
|
|
1128
|
+
code: MiniappErrorCode.NOT_IMPLEMENTED,
|
|
1129
|
+
message: `Unknown or unimplemented request type: ${requestType}`,
|
|
1130
|
+
});
|
|
982
1131
|
break;
|
|
983
1132
|
}
|
|
984
1133
|
}
|
|
@@ -995,6 +1144,7 @@ class LocalMiniappRuntime {
|
|
|
995
1144
|
}
|
|
996
1145
|
// Update lastPongAt so it doesn't time out right away
|
|
997
1146
|
existing.lastPongAt = Date.now();
|
|
1147
|
+
existing.unansweredPingRounds = 0;
|
|
998
1148
|
// Read current glasses capabilities from the settings store
|
|
999
1149
|
const defaultWearable = useSettingsStore.getState().getSetting(ISLAND_SETTINGS_KEYS.defaultWearable);
|
|
1000
1150
|
const capabilities = getModelCapabilities(defaultWearable || DeviceTypes.NONE);
|
|
@@ -1182,6 +1332,51 @@ class LocalMiniappRuntime {
|
|
|
1182
1332
|
this.deliverInitialMiniappAuth(packageName, undefined, 0);
|
|
1183
1333
|
}
|
|
1184
1334
|
}
|
|
1335
|
+
/**
|
|
1336
|
+
* TRANSCRIPTION_CONFIG (issue 021 WP3): store the miniapp's language hints
|
|
1337
|
+
* and rebuild the cloud subscription set so the auto-mode subscription
|
|
1338
|
+
* carries them. Replies with a REQUEST_RESULT so the SDK's configure()
|
|
1339
|
+
* promise settles: unknown hint values are an error, not a silent drop.
|
|
1340
|
+
*/
|
|
1341
|
+
handleTranscriptionConfig(packageName, payload, requestId) {
|
|
1342
|
+
const app = this.connectedApps.get(packageName);
|
|
1343
|
+
if (!app)
|
|
1344
|
+
return;
|
|
1345
|
+
const config = (payload.config ?? {});
|
|
1346
|
+
const rawHints = config.languageHints;
|
|
1347
|
+
if (rawHints !== undefined && !Array.isArray(rawHints)) {
|
|
1348
|
+
this.sendResult(packageName, requestId, false, undefined, {
|
|
1349
|
+
code: MiniappErrorCode.INTERNAL,
|
|
1350
|
+
message: "languageHints must be an array of language codes",
|
|
1351
|
+
});
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
if (rawHints === undefined || rawHints.length === 0) {
|
|
1355
|
+
this.transcriptionHintsByApp.delete(packageName);
|
|
1356
|
+
}
|
|
1357
|
+
else {
|
|
1358
|
+
// Defense in depth: the SDK already validated, but the wire is untyped.
|
|
1359
|
+
const hints = [];
|
|
1360
|
+
const invalid = [];
|
|
1361
|
+
for (const raw of rawHints) {
|
|
1362
|
+
const hint = typeof raw === "string" ? toLanguageHint(raw) : null;
|
|
1363
|
+
if (hint)
|
|
1364
|
+
hints.push(hint);
|
|
1365
|
+
else
|
|
1366
|
+
invalid.push(String(raw));
|
|
1367
|
+
}
|
|
1368
|
+
if (invalid.length > 0) {
|
|
1369
|
+
this.sendResult(packageName, requestId, false, undefined, {
|
|
1370
|
+
code: MiniappErrorCode.INTERNAL,
|
|
1371
|
+
message: `Unknown language hint(s): ${invalid.join(", ")}`,
|
|
1372
|
+
});
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
this.transcriptionHintsByApp.set(packageName, [...new Set(hints)].sort());
|
|
1376
|
+
}
|
|
1377
|
+
this.updateCloudSubscriptions();
|
|
1378
|
+
this.sendResult(packageName, requestId, true, { applied: true });
|
|
1379
|
+
}
|
|
1185
1380
|
handleSubscribe(packageName, payload, requestId) {
|
|
1186
1381
|
const app = this.connectedApps.get(packageName);
|
|
1187
1382
|
if (!app)
|
|
@@ -1254,28 +1449,12 @@ class LocalMiniappRuntime {
|
|
|
1254
1449
|
return;
|
|
1255
1450
|
}
|
|
1256
1451
|
}
|
|
1257
|
-
// Remove old subscriptions for this app
|
|
1258
|
-
for (const oldStream of app.subscriptions) {
|
|
1259
|
-
const subs = this.streamSubscribers.get(oldStream);
|
|
1260
|
-
if (subs) {
|
|
1261
|
-
subs.delete(packageName);
|
|
1262
|
-
if (subs.size === 0) {
|
|
1263
|
-
this.streamSubscribers.delete(oldStream);
|
|
1264
|
-
}
|
|
1265
|
-
}
|
|
1266
|
-
}
|
|
1267
1452
|
// Add new subscriptions
|
|
1453
|
+
const previousSubscriptions = app.subscriptions;
|
|
1268
1454
|
app.subscriptions = new Set(streams);
|
|
1269
1455
|
app.forceLocalTranscriptionStreams = new Set([...forceLocalTranscriptionStreams].filter((stream) => app.subscriptions.has(stream)));
|
|
1270
1456
|
app.cloudTranscriptionStreams = new Set([...cloudTranscriptionStreams].filter((stream) => app.subscriptions.has(stream)));
|
|
1271
|
-
|
|
1272
|
-
let subs = this.streamSubscribers.get(stream);
|
|
1273
|
-
if (!subs) {
|
|
1274
|
-
subs = new Set();
|
|
1275
|
-
this.streamSubscribers.set(stream, subs);
|
|
1276
|
-
}
|
|
1277
|
-
subs.add(packageName);
|
|
1278
|
-
}
|
|
1457
|
+
this.replaceStreamSubscribers(packageName, previousSubscriptions, app.subscriptions);
|
|
1279
1458
|
this.recomputeMicRequirements();
|
|
1280
1459
|
this.updateCloudSubscriptions();
|
|
1281
1460
|
this.recomputeHeadingSubscription();
|
|
@@ -1718,15 +1897,20 @@ class LocalMiniappRuntime {
|
|
|
1718
1897
|
}
|
|
1719
1898
|
async handleSpeak(packageName, payload, requestId) {
|
|
1720
1899
|
const rawText = payload.text;
|
|
1721
|
-
const
|
|
1900
|
+
const rawSentences = typeof rawText === "string"
|
|
1722
1901
|
? [rawText.trim()].filter(Boolean)
|
|
1723
1902
|
: Array.isArray(rawText)
|
|
1724
|
-
? rawText
|
|
1903
|
+
? rawText
|
|
1904
|
+
.filter((sentence) => typeof sentence === "string")
|
|
1905
|
+
.map((sentence) => sentence.trim())
|
|
1906
|
+
.filter(Boolean)
|
|
1725
1907
|
: [];
|
|
1908
|
+
const enableSanitization = payload.enableSanitization !== false;
|
|
1909
|
+
const sentences = prepareTtsSentences(rawSentences, enableSanitization);
|
|
1726
1910
|
if (sentences.length === 0) {
|
|
1727
1911
|
this.sendResult(packageName, requestId, false, undefined, {
|
|
1728
1912
|
code: MiniappErrorCode.INTERNAL,
|
|
1729
|
-
message: "speak requires text or a non-empty sentence list",
|
|
1913
|
+
message: "speak requires text or a non-empty sentence list with speakable content",
|
|
1730
1914
|
});
|
|
1731
1915
|
return;
|
|
1732
1916
|
}
|
|
@@ -1819,7 +2003,6 @@ class LocalMiniappRuntime {
|
|
|
1819
2003
|
appId: packageName,
|
|
1820
2004
|
volume,
|
|
1821
2005
|
stopOtherAudio,
|
|
1822
|
-
suppressCloudUplink: true,
|
|
1823
2006
|
}, (_responseId, success, error, duration, completionReason) => {
|
|
1824
2007
|
if (run.playbackRequestId === sentenceRequestId)
|
|
1825
2008
|
run.playbackRequestId = undefined;
|
|
@@ -1863,7 +2046,6 @@ class LocalMiniappRuntime {
|
|
|
1863
2046
|
appId: packageName,
|
|
1864
2047
|
volume,
|
|
1865
2048
|
stopOtherAudio,
|
|
1866
|
-
suppressCloudUplink: true,
|
|
1867
2049
|
}, (_respId, success, error, duration, completionReason) => {
|
|
1868
2050
|
if (run.playbackRequestId === audioRequestId)
|
|
1869
2051
|
run.playbackRequestId = undefined;
|
|
@@ -1909,7 +2091,6 @@ class LocalMiniappRuntime {
|
|
|
1909
2091
|
appId: packageName,
|
|
1910
2092
|
volume,
|
|
1911
2093
|
stopOtherAudio,
|
|
1912
|
-
suppressCloudUplink: true,
|
|
1913
2094
|
}, (_respId, success, error, duration, completionReason) => {
|
|
1914
2095
|
if (run.playbackRequestId === audioRequestId)
|
|
1915
2096
|
run.playbackRequestId = undefined;
|
|
@@ -1990,7 +2171,15 @@ class LocalMiniappRuntime {
|
|
|
1990
2171
|
}
|
|
1991
2172
|
async handleLocationPoll(packageName, requestId) {
|
|
1992
2173
|
try {
|
|
1993
|
-
|
|
2174
|
+
// Expo's Android requestForegroundPermissionsAsync always delegates to
|
|
2175
|
+
// Activity.requestPermissions(), even when permission is already granted.
|
|
2176
|
+
// Calling it after the user presses Home therefore waits until the Activity
|
|
2177
|
+
// resumes. Read the current grant first, and only enter the prompt flow
|
|
2178
|
+
// while the Activity is actually foregrounded.
|
|
2179
|
+
const permissionStartedAt = Date.now();
|
|
2180
|
+
const permission = await resolveForegroundLocationPermission(Location, () => AppState.currentState);
|
|
2181
|
+
const { status } = permission;
|
|
2182
|
+
console.log(`${LOG_TAG}: location poll permission status=${status} appState=${AppState.currentState} elapsed=${Date.now() - permissionStartedAt}ms`);
|
|
1994
2183
|
if (status !== "granted") {
|
|
1995
2184
|
this.sendResult(packageName, requestId, false, undefined, {
|
|
1996
2185
|
code: MiniappErrorCode.PERMISSION_NOT_DECLARED,
|
|
@@ -2002,8 +2191,11 @@ class LocalMiniappRuntime {
|
|
|
2002
2191
|
// a fresh fix if the cache is empty. Use Low accuracy on the fresh
|
|
2003
2192
|
// path so we get a cell/wifi-tower fix in ~1s instead of waiting
|
|
2004
2193
|
// for full GPS warm-up.
|
|
2194
|
+
const locationStartedAt = Date.now();
|
|
2005
2195
|
const cached = await Location.getLastKnownPositionAsync({ maxAge: 60_000 });
|
|
2196
|
+
console.log(`${LOG_TAG}: location poll cache hit=${cached !== null} elapsed=${Date.now() - locationStartedAt}ms`);
|
|
2006
2197
|
const location = cached ?? (await Location.getCurrentPositionAsync({ accuracy: Location.Accuracy.Low }));
|
|
2198
|
+
console.log(`${LOG_TAG}: location poll resolved elapsed=${Date.now() - locationStartedAt}ms`);
|
|
2007
2199
|
this.sendResult(packageName, requestId, true, {
|
|
2008
2200
|
lat: location.coords.latitude,
|
|
2009
2201
|
lng: location.coords.longitude,
|
|
@@ -2038,8 +2230,64 @@ class LocalMiniappRuntime {
|
|
|
2038
2230
|
*/
|
|
2039
2231
|
blobStore = new BlobStore({
|
|
2040
2232
|
sendResult: (packageName, requestId, ok, result, error) => this.sendResult(packageName, requestId, ok, result, error),
|
|
2041
|
-
getUserId: () =>
|
|
2233
|
+
getUserId: () => cloudClientService.getMentraUserId(),
|
|
2234
|
+
});
|
|
2235
|
+
blobRequestQueues = new Map();
|
|
2236
|
+
simpleStorage = new LocalMiniappStorage({
|
|
2237
|
+
backend: localMiniappStorageBackend,
|
|
2238
|
+
getUserId: () => cloudClientService.resolveMentraUserId(),
|
|
2042
2239
|
});
|
|
2240
|
+
/**
|
|
2241
|
+
* BlobStore's file helpers are synchronous once a user namespace is selected,
|
|
2242
|
+
* but first-boot Core auth is not. Queue requests per app behind stable
|
|
2243
|
+
* identity resolution so CREATE/WRITE/COMMIT ordering survives that wait.
|
|
2244
|
+
*/
|
|
2245
|
+
enqueueBlobRequest(packageName, requestId, operation) {
|
|
2246
|
+
const app = this.connectedApps.get(packageName);
|
|
2247
|
+
const previous = this.blobRequestQueues.get(packageName) ?? Promise.resolve();
|
|
2248
|
+
const queued = previous.then(async () => {
|
|
2249
|
+
try {
|
|
2250
|
+
await cloudClientService.resolveMentraUserId();
|
|
2251
|
+
}
|
|
2252
|
+
catch (err) {
|
|
2253
|
+
console.error(`${LOG_TAG}: blob identity error:`, err);
|
|
2254
|
+
this.sendResult(packageName, requestId, false, undefined, {
|
|
2255
|
+
code: MiniappErrorCode.NOT_CONNECTED,
|
|
2256
|
+
message: "Blob storage is unavailable before sign-in completes",
|
|
2257
|
+
});
|
|
2258
|
+
return;
|
|
2259
|
+
}
|
|
2260
|
+
// unregisterApp() or a crash-respawn may have replaced this app while
|
|
2261
|
+
// identity resolution was pending. Never replay an old context's request
|
|
2262
|
+
// against its replacement.
|
|
2263
|
+
if (!app || this.connectedApps.get(packageName) !== app)
|
|
2264
|
+
return;
|
|
2265
|
+
// Invoke in request order, but do not keep the queue occupied for the
|
|
2266
|
+
// lifetime of an async picker, download, or share sheet. CREATE/WRITE/
|
|
2267
|
+
// COMMIT are synchronous and therefore still dispatch strictly in order.
|
|
2268
|
+
try {
|
|
2269
|
+
const pending = operation();
|
|
2270
|
+
if (pending) {
|
|
2271
|
+
void pending.catch((err) => this.handleBlobRequestError(packageName, requestId, err));
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
catch (err) {
|
|
2275
|
+
this.handleBlobRequestError(packageName, requestId, err);
|
|
2276
|
+
}
|
|
2277
|
+
});
|
|
2278
|
+
this.blobRequestQueues.set(packageName, queued);
|
|
2279
|
+
void queued.finally(() => {
|
|
2280
|
+
if (this.blobRequestQueues.get(packageName) === queued)
|
|
2281
|
+
this.blobRequestQueues.delete(packageName);
|
|
2282
|
+
});
|
|
2283
|
+
}
|
|
2284
|
+
handleBlobRequestError(packageName, requestId, err) {
|
|
2285
|
+
console.error(`${LOG_TAG}: blob request error:`, err);
|
|
2286
|
+
this.sendResult(packageName, requestId, false, undefined, {
|
|
2287
|
+
code: MiniappErrorCode.INTERNAL,
|
|
2288
|
+
message: err instanceof Error ? err.message : "Blob storage error",
|
|
2289
|
+
});
|
|
2290
|
+
}
|
|
2043
2291
|
/**
|
|
2044
2292
|
* Heading is a sensor stream — start the native compass when any mini
|
|
2045
2293
|
* app is subscribed to "heading_update", stop it when none are.
|
|
@@ -2114,10 +2362,6 @@ class LocalMiniappRuntime {
|
|
|
2114
2362
|
// ---------------------------------------------------------------------------
|
|
2115
2363
|
// Storage helpers
|
|
2116
2364
|
// ---------------------------------------------------------------------------
|
|
2117
|
-
getStorageKeyPrefix(packageName) {
|
|
2118
|
-
const userId = useSettingsStore.getState().getSetting(ISLAND_SETTINGS_KEYS.coreToken) || "anonymous";
|
|
2119
|
-
return `mentraos_localstorage_${userId}_${packageName}_`;
|
|
2120
|
-
}
|
|
2121
2365
|
async handleStorageGet(packageName, payload, requestId) {
|
|
2122
2366
|
try {
|
|
2123
2367
|
const key = payload.key;
|
|
@@ -2128,9 +2372,8 @@ class LocalMiniappRuntime {
|
|
|
2128
2372
|
});
|
|
2129
2373
|
return;
|
|
2130
2374
|
}
|
|
2131
|
-
const
|
|
2132
|
-
|
|
2133
|
-
this.sendResult(packageName, requestId, true, { key, value: result.is_ok() ? result.value : null });
|
|
2375
|
+
const value = await this.simpleStorage.get(packageName, key);
|
|
2376
|
+
this.sendResult(packageName, requestId, true, { key, value });
|
|
2134
2377
|
}
|
|
2135
2378
|
catch (err) {
|
|
2136
2379
|
console.error(`${LOG_TAG}: storage_get error:`, err);
|
|
@@ -2150,8 +2393,7 @@ class LocalMiniappRuntime {
|
|
|
2150
2393
|
});
|
|
2151
2394
|
return;
|
|
2152
2395
|
}
|
|
2153
|
-
|
|
2154
|
-
mmkvStorage.save(fullKey, payload.value ?? null);
|
|
2396
|
+
await this.simpleStorage.set(packageName, key, payload.value ?? null);
|
|
2155
2397
|
this.sendResult(packageName, requestId, true);
|
|
2156
2398
|
}
|
|
2157
2399
|
catch (err) {
|
|
@@ -2172,8 +2414,7 @@ class LocalMiniappRuntime {
|
|
|
2172
2414
|
});
|
|
2173
2415
|
return;
|
|
2174
2416
|
}
|
|
2175
|
-
|
|
2176
|
-
mmkvStorage.remove(fullKey);
|
|
2417
|
+
await this.simpleStorage.delete(packageName, key);
|
|
2177
2418
|
this.sendResult(packageName, requestId, true);
|
|
2178
2419
|
}
|
|
2179
2420
|
catch (err) {
|
|
@@ -2186,9 +2427,7 @@ class LocalMiniappRuntime {
|
|
|
2186
2427
|
}
|
|
2187
2428
|
async handleStorageList(packageName, _payload, requestId) {
|
|
2188
2429
|
try {
|
|
2189
|
-
const
|
|
2190
|
-
const allKeys = mmkvStorage.getAllKeys();
|
|
2191
|
-
const keys = allKeys.filter((k) => k.startsWith(prefix)).map((k) => k.slice(prefix.length));
|
|
2430
|
+
const keys = await this.simpleStorage.keys(packageName);
|
|
2192
2431
|
this.sendResult(packageName, requestId, true, { keys });
|
|
2193
2432
|
}
|
|
2194
2433
|
catch (err) {
|
|
@@ -2201,12 +2440,7 @@ class LocalMiniappRuntime {
|
|
|
2201
2440
|
}
|
|
2202
2441
|
async handleStorageClear(packageName, requestId) {
|
|
2203
2442
|
try {
|
|
2204
|
-
|
|
2205
|
-
const allKeys = mmkvStorage.getAllKeys();
|
|
2206
|
-
for (const k of allKeys) {
|
|
2207
|
-
if (k.startsWith(prefix))
|
|
2208
|
-
mmkvStorage.remove(k);
|
|
2209
|
-
}
|
|
2443
|
+
await this.simpleStorage.clear(packageName);
|
|
2210
2444
|
this.sendResult(packageName, requestId, true);
|
|
2211
2445
|
}
|
|
2212
2446
|
catch (err) {
|
|
@@ -2227,9 +2461,8 @@ class LocalMiniappRuntime {
|
|
|
2227
2461
|
});
|
|
2228
2462
|
return;
|
|
2229
2463
|
}
|
|
2230
|
-
const
|
|
2231
|
-
|
|
2232
|
-
this.sendResult(packageName, requestId, true, { has: result.is_ok() });
|
|
2464
|
+
const has = await this.simpleStorage.has(packageName, key);
|
|
2465
|
+
this.sendResult(packageName, requestId, true, { has });
|
|
2233
2466
|
}
|
|
2234
2467
|
catch (err) {
|
|
2235
2468
|
console.error(`${LOG_TAG}: storage_has error:`, err);
|
|
@@ -2241,18 +2474,7 @@ class LocalMiniappRuntime {
|
|
|
2241
2474
|
}
|
|
2242
2475
|
async handleStorageGetAll(packageName, requestId) {
|
|
2243
2476
|
try {
|
|
2244
|
-
const
|
|
2245
|
-
const allKeys = mmkvStorage.getAllKeys();
|
|
2246
|
-
const values = {};
|
|
2247
|
-
for (const k of allKeys) {
|
|
2248
|
-
if (!k.startsWith(prefix))
|
|
2249
|
-
continue;
|
|
2250
|
-
const r = mmkvStorage.load(k);
|
|
2251
|
-
if (r.is_ok()) {
|
|
2252
|
-
const v = r.value;
|
|
2253
|
-
values[k.slice(prefix.length)] = typeof v === "string" ? v : String(v ?? "");
|
|
2254
|
-
}
|
|
2255
|
-
}
|
|
2477
|
+
const values = await this.simpleStorage.getAll(packageName);
|
|
2256
2478
|
this.sendResult(packageName, requestId, true, { values });
|
|
2257
2479
|
}
|
|
2258
2480
|
catch (err) {
|
|
@@ -2273,10 +2495,7 @@ class LocalMiniappRuntime {
|
|
|
2273
2495
|
});
|
|
2274
2496
|
return;
|
|
2275
2497
|
}
|
|
2276
|
-
|
|
2277
|
-
for (const [key, value] of Object.entries(values)) {
|
|
2278
|
-
mmkvStorage.save(prefix + key, value ?? null);
|
|
2279
|
-
}
|
|
2498
|
+
await this.simpleStorage.setMultiple(packageName, values);
|
|
2280
2499
|
this.sendResult(packageName, requestId, true);
|
|
2281
2500
|
}
|
|
2282
2501
|
catch (err) {
|
|
@@ -2288,6 +2507,29 @@ class LocalMiniappRuntime {
|
|
|
2288
2507
|
}
|
|
2289
2508
|
}
|
|
2290
2509
|
// ---------------------------------------------------------------------------
|
|
2510
|
+
// Wi-Fi ADB
|
|
2511
|
+
// ---------------------------------------------------------------------------
|
|
2512
|
+
async handleSetWifiAdbState(packageName, payload, requestId) {
|
|
2513
|
+
const result = await handleWifiAdbRequest(packageName, payload, {
|
|
2514
|
+
isSystemPackage: (candidate) => this.isSystemPackage(candidate),
|
|
2515
|
+
setWifiAdbState: async (enabled) => {
|
|
2516
|
+
console.log(`${LOG_TAG}: set_wifi_adb_state enabled=${enabled} from ${packageName}`);
|
|
2517
|
+
try {
|
|
2518
|
+
await BluetoothSdk.setWifiAdbState(enabled);
|
|
2519
|
+
}
|
|
2520
|
+
catch (error) {
|
|
2521
|
+
console.error(`${LOG_TAG}: set_wifi_adb_state error:`, error);
|
|
2522
|
+
throw error;
|
|
2523
|
+
}
|
|
2524
|
+
},
|
|
2525
|
+
});
|
|
2526
|
+
if (result.ok) {
|
|
2527
|
+
this.sendResult(packageName, requestId, true);
|
|
2528
|
+
return;
|
|
2529
|
+
}
|
|
2530
|
+
this.sendResult(packageName, requestId, false, undefined, result.error);
|
|
2531
|
+
}
|
|
2532
|
+
// ---------------------------------------------------------------------------
|
|
2291
2533
|
// Camera FOV
|
|
2292
2534
|
// ---------------------------------------------------------------------------
|
|
2293
2535
|
async handleCameraFov(packageName, payload, requestId) {
|
|
@@ -2344,6 +2586,103 @@ class LocalMiniappRuntime {
|
|
|
2344
2586
|
});
|
|
2345
2587
|
}
|
|
2346
2588
|
}
|
|
2589
|
+
/**
|
|
2590
|
+
* Lifecycle-scoped glasses-side VAD override from a miniapp
|
|
2591
|
+
* (session.mic.setVoiceActivityDetectionEnabled). Requires MICROPHONE in the
|
|
2592
|
+
* miniapp manifest. Mentra Live only today — other models no-op via DeviceStore.
|
|
2593
|
+
*/
|
|
2594
|
+
async handleMicSetVadEnabled(packageName, payload, requestId) {
|
|
2595
|
+
const app = this.connectedApps.get(packageName);
|
|
2596
|
+
const hasMicPermission = app?.installedManifest?.permissions?.some((p) => p.type === "MICROPHONE");
|
|
2597
|
+
if (!hasMicPermission) {
|
|
2598
|
+
logPermissionNotDeclared(packageName, "MICROPHONE", "to set VAD enabled", `{"type": "MICROPHONE"}`);
|
|
2599
|
+
this.sendResult(packageName, requestId, false, undefined, {
|
|
2600
|
+
code: MiniappErrorCode.PERMISSION_NOT_DECLARED,
|
|
2601
|
+
message: `MICROPHONE permission not declared in miniapp.json. Add {"type": "MICROPHONE"} to the "permissions" array.`,
|
|
2602
|
+
permission: "MICROPHONE",
|
|
2603
|
+
operation: MiniappRequestType.MIC_SET_VAD_ENABLED,
|
|
2604
|
+
});
|
|
2605
|
+
return;
|
|
2606
|
+
}
|
|
2607
|
+
if (typeof payload.enabled !== "boolean") {
|
|
2608
|
+
this.sendResult(packageName, requestId, false, undefined, {
|
|
2609
|
+
code: MiniappErrorCode.INVALID_ARGUMENT,
|
|
2610
|
+
message: "enabled must be a boolean",
|
|
2611
|
+
});
|
|
2612
|
+
return;
|
|
2613
|
+
}
|
|
2614
|
+
try {
|
|
2615
|
+
const enabled = payload.enabled;
|
|
2616
|
+
console.log(`${LOG_TAG}: mic_set_vad_enabled ${enabled} (by ${packageName})`);
|
|
2617
|
+
await micStateCoordinator.setMiniappGateOverride(packageName, "vad", enabled, this.getConfiguredMicGates());
|
|
2618
|
+
this.sendResult(packageName, requestId, true);
|
|
2619
|
+
}
|
|
2620
|
+
catch (err) {
|
|
2621
|
+
console.error(`${LOG_TAG}: mic_set_vad_enabled error:`, err);
|
|
2622
|
+
this.sendResult(packageName, requestId, false, undefined, {
|
|
2623
|
+
code: MiniappErrorCode.INTERNAL,
|
|
2624
|
+
message: err instanceof Error ? err.message : "VAD set-enabled error",
|
|
2625
|
+
});
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
/**
|
|
2629
|
+
* Lifecycle-scoped center-mic loudness gate ("Barrier") override from a miniapp
|
|
2630
|
+
* (session.mic.setLoudnessGateEnabled). Requires MICROPHONE in the miniapp
|
|
2631
|
+
* manifest. Mentra Live only today — other models no-op via DeviceStore.
|
|
2632
|
+
*/
|
|
2633
|
+
async handleMicSetLoudnessGateEnabled(packageName, payload, requestId) {
|
|
2634
|
+
const app = this.connectedApps.get(packageName);
|
|
2635
|
+
const hasMicPermission = app?.installedManifest?.permissions?.some((p) => p.type === "MICROPHONE");
|
|
2636
|
+
if (!hasMicPermission) {
|
|
2637
|
+
logPermissionNotDeclared(packageName, "MICROPHONE", "to set loudness gate enabled", `{"type": "MICROPHONE"}`);
|
|
2638
|
+
this.sendResult(packageName, requestId, false, undefined, {
|
|
2639
|
+
code: MiniappErrorCode.PERMISSION_NOT_DECLARED,
|
|
2640
|
+
message: `MICROPHONE permission not declared in miniapp.json. Add {"type": "MICROPHONE"} to the "permissions" array.`,
|
|
2641
|
+
permission: "MICROPHONE",
|
|
2642
|
+
operation: MiniappRequestType.MIC_SET_LOUDNESS_GATE_ENABLED,
|
|
2643
|
+
});
|
|
2644
|
+
return;
|
|
2645
|
+
}
|
|
2646
|
+
if (typeof payload.enabled !== "boolean") {
|
|
2647
|
+
this.sendResult(packageName, requestId, false, undefined, {
|
|
2648
|
+
code: MiniappErrorCode.INVALID_ARGUMENT,
|
|
2649
|
+
message: "enabled must be a boolean",
|
|
2650
|
+
});
|
|
2651
|
+
return;
|
|
2652
|
+
}
|
|
2653
|
+
try {
|
|
2654
|
+
const enabled = payload.enabled;
|
|
2655
|
+
console.log(`${LOG_TAG}: mic_set_loudness_gate_enabled ${enabled} (by ${packageName})`);
|
|
2656
|
+
await micStateCoordinator.setMiniappGateOverride(packageName, "loudnessGate", enabled, this.getConfiguredMicGates());
|
|
2657
|
+
this.sendResult(packageName, requestId, true);
|
|
2658
|
+
}
|
|
2659
|
+
catch (err) {
|
|
2660
|
+
console.error(`${LOG_TAG}: mic_set_loudness_gate_enabled error:`, err);
|
|
2661
|
+
this.sendResult(packageName, requestId, false, undefined, {
|
|
2662
|
+
code: MiniappErrorCode.INTERNAL,
|
|
2663
|
+
message: err instanceof Error ? err.message : "Loudness gate set-enabled error",
|
|
2664
|
+
});
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
/**
|
|
2668
|
+
* session.system.scanQr — host camera overlay. Must not clear miniapp
|
|
2669
|
+
* foreground; the host seam is responsible for presenting a Modal on top.
|
|
2670
|
+
*/
|
|
2671
|
+
async handleScanQr(packageName, payload, requestId) {
|
|
2672
|
+
try {
|
|
2673
|
+
const result = await invokeScanQrSeam(getUiSeams().scanQr, payload);
|
|
2674
|
+
this.sendResult(packageName, requestId, true, result);
|
|
2675
|
+
}
|
|
2676
|
+
catch (err) {
|
|
2677
|
+
if (!getUiSeams().scanQr) {
|
|
2678
|
+
console.warn(`${LOG_TAG}: scanQr seam missing — QR overlay is not registered`);
|
|
2679
|
+
}
|
|
2680
|
+
this.sendResult(packageName, requestId, false, undefined, {
|
|
2681
|
+
code: err?.code || MiniappErrorCode.INTERNAL,
|
|
2682
|
+
message: err instanceof Error ? err.message : "QR scan failed",
|
|
2683
|
+
});
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2347
2686
|
// ---------------------------------------------------------------------------
|
|
2348
2687
|
// System utilities (share, open URL, clipboard, download)
|
|
2349
2688
|
// ---------------------------------------------------------------------------
|
|
@@ -2481,8 +2820,8 @@ class LocalMiniappRuntime {
|
|
|
2481
2820
|
isoCap: payload.isoCap,
|
|
2482
2821
|
noiseReduction: payload.noiseReduction,
|
|
2483
2822
|
edgeEnhancement: payload.edgeEnhancement,
|
|
2484
|
-
mfnr: payload.mfnr,
|
|
2485
2823
|
zsl: payload.zsl,
|
|
2824
|
+
mfnr: payload.mfnr,
|
|
2486
2825
|
ispDigitalGain: payload.ispDigitalGain,
|
|
2487
2826
|
ispAnalogGain: payload.ispAnalogGain,
|
|
2488
2827
|
});
|
|
@@ -2517,6 +2856,8 @@ class LocalMiniappRuntime {
|
|
|
2517
2856
|
mode: payload.mode,
|
|
2518
2857
|
exposureTimeNs: payload.exposureTimeNs,
|
|
2519
2858
|
durationMs: payload.durationMs,
|
|
2859
|
+
zsl: payload.zsl,
|
|
2860
|
+
mfnr: payload.mfnr,
|
|
2520
2861
|
});
|
|
2521
2862
|
this.sendResult(packageName, requestId, true);
|
|
2522
2863
|
}
|
|
@@ -2596,6 +2937,7 @@ class LocalMiniappRuntime {
|
|
|
2596
2937
|
video: normalizeStreamVideoConfig(payload.video),
|
|
2597
2938
|
audio: normalizeStreamAudioConfig(payload.audio),
|
|
2598
2939
|
sound: payload.sound,
|
|
2940
|
+
authToken: typeof payload.authToken === "string" ? payload.authToken : undefined,
|
|
2599
2941
|
});
|
|
2600
2942
|
this.sendResult(packageName, requestId, true, result);
|
|
2601
2943
|
}
|
|
@@ -2674,7 +3016,7 @@ class LocalMiniappRuntime {
|
|
|
2674
3016
|
return;
|
|
2675
3017
|
}
|
|
2676
3018
|
try {
|
|
2677
|
-
await requestWifiSetup(payload.reason);
|
|
3019
|
+
await requestWifiSetup(payload.reason, packageName);
|
|
2678
3020
|
this.sendResult(packageName, requestId, true);
|
|
2679
3021
|
}
|
|
2680
3022
|
catch (err) {
|
|
@@ -2733,28 +3075,12 @@ class LocalMiniappRuntime {
|
|
|
2733
3075
|
return { ok: false, error: "MICROPHONE permission not declared in miniapp.json" };
|
|
2734
3076
|
}
|
|
2735
3077
|
}
|
|
2736
|
-
// Remove old subscriptions for this app
|
|
2737
|
-
for (const oldStream of app.subscriptions) {
|
|
2738
|
-
const subs = this.streamSubscribers.get(oldStream);
|
|
2739
|
-
if (subs) {
|
|
2740
|
-
subs.delete(packageName);
|
|
2741
|
-
if (subs.size === 0) {
|
|
2742
|
-
this.streamSubscribers.delete(oldStream);
|
|
2743
|
-
}
|
|
2744
|
-
}
|
|
2745
|
-
}
|
|
2746
3078
|
// Add new subscriptions
|
|
3079
|
+
const previousSubscriptions = app.subscriptions;
|
|
2747
3080
|
app.subscriptions = new Set(streams);
|
|
2748
3081
|
app.forceLocalTranscriptionStreams.clear();
|
|
2749
3082
|
app.cloudTranscriptionStreams = new Set(streams.filter((stream) => stream.startsWith("transcription:")));
|
|
2750
|
-
|
|
2751
|
-
let subs = this.streamSubscribers.get(stream);
|
|
2752
|
-
if (!subs) {
|
|
2753
|
-
subs = new Set();
|
|
2754
|
-
this.streamSubscribers.set(stream, subs);
|
|
2755
|
-
}
|
|
2756
|
-
subs.add(packageName);
|
|
2757
|
-
}
|
|
3083
|
+
this.replaceStreamSubscribers(packageName, previousSubscriptions, app.subscriptions);
|
|
2758
3084
|
this.recomputeMicRequirements();
|
|
2759
3085
|
this.updateCloudSubscriptions();
|
|
2760
3086
|
return { ok: true };
|
|
@@ -2762,6 +3088,15 @@ class LocalMiniappRuntime {
|
|
|
2762
3088
|
// ===========================================================================
|
|
2763
3089
|
// Mic requirements
|
|
2764
3090
|
// ===========================================================================
|
|
3091
|
+
getConfiguredMicGates() {
|
|
3092
|
+
const bluetoothSettings = useSettingsStore.getState().getBluetoothSettings();
|
|
3093
|
+
const configuredVad = bluetoothSettings.voice_activity_detection_enabled;
|
|
3094
|
+
const configuredLoudnessGate = bluetoothSettings.loudness_gate_enabled;
|
|
3095
|
+
return {
|
|
3096
|
+
vadEnabled: typeof configuredVad === "boolean" ? configuredVad : null,
|
|
3097
|
+
loudnessGateEnabled: typeof configuredLoudnessGate === "boolean" ? configuredLoudnessGate : null,
|
|
3098
|
+
};
|
|
3099
|
+
}
|
|
2765
3100
|
recomputeMicRequirements() {
|
|
2766
3101
|
let anyPcm = false;
|
|
2767
3102
|
let anyLc3 = false;
|
|
@@ -2773,7 +3108,11 @@ class LocalMiniappRuntime {
|
|
|
2773
3108
|
if (stream.startsWith("transcription:") || stream.startsWith("translation:") || stream === "vad")
|
|
2774
3109
|
anyLc3 = true;
|
|
2775
3110
|
}
|
|
2776
|
-
micStateCoordinator.setLocalRequirements({
|
|
3111
|
+
micStateCoordinator.setLocalRequirements({
|
|
3112
|
+
pcm: anyPcm,
|
|
3113
|
+
lc3: anyLc3,
|
|
3114
|
+
...this.getConfiguredMicGates(),
|
|
3115
|
+
});
|
|
2777
3116
|
}
|
|
2778
3117
|
/**
|
|
2779
3118
|
* Recompute the aggregated v2 cloud subscription list across all local miniapps.
|
|
@@ -2844,11 +3183,20 @@ class LocalMiniappRuntime {
|
|
|
2844
3183
|
*/
|
|
2845
3184
|
buildCloudAudioSubscriptions(cloudStreams) {
|
|
2846
3185
|
const subs = [];
|
|
3186
|
+
// Union of every connected app's TRANSCRIPTION_CONFIG hints. Hints only
|
|
3187
|
+
// apply to the auto-mode TRANSCRIPTION subscription (the wire schema has
|
|
3188
|
+
// no hints field on specific-language sources — the language IS the hint
|
|
3189
|
+
// there), and are deliberately NOT attached to translation sources.
|
|
3190
|
+
const hintUnion = [...new Set([...this.transcriptionHintsByApp.values()].flat())].sort();
|
|
2847
3191
|
const langSource = (code) => code === "auto" || code === "*" ? { mode: "auto" } : { mode: "specific", code };
|
|
2848
3192
|
for (const stream of cloudStreams) {
|
|
2849
3193
|
if (stream.startsWith("transcription:")) {
|
|
2850
3194
|
const lang = stream.substring("transcription:".length);
|
|
2851
|
-
|
|
3195
|
+
const source = langSource(lang);
|
|
3196
|
+
subs.push({
|
|
3197
|
+
kind: "transcription",
|
|
3198
|
+
language: source.mode === "auto" && hintUnion.length > 0 ? { mode: "auto", hints: hintUnion } : source,
|
|
3199
|
+
});
|
|
2852
3200
|
}
|
|
2853
3201
|
else if (stream.startsWith("translation:")) {
|
|
2854
3202
|
const parts = stream.split(":");
|
|
@@ -3623,15 +3971,15 @@ class LocalMiniappRuntime {
|
|
|
3623
3971
|
}
|
|
3624
3972
|
}
|
|
3625
3973
|
doPingRound() {
|
|
3626
|
-
const now = Date.now();
|
|
3627
|
-
const staleThreshold = PING_INTERVAL_MS * PING_TIMEOUT_THRESHOLD;
|
|
3628
3974
|
const toRemove = [];
|
|
3629
3975
|
for (const [packageName, app] of this.connectedApps) {
|
|
3630
|
-
|
|
3976
|
+
const liveness = advanceMiniappPingLiveness(app.unansweredPingRounds, PING_TIMEOUT_THRESHOLD);
|
|
3977
|
+
if (liveness.shouldUnregister) {
|
|
3631
3978
|
console.warn(`${LOG_TAG}: ${packageName} missed ${PING_TIMEOUT_THRESHOLD} pings, unregistering`);
|
|
3632
3979
|
toRemove.push(packageName);
|
|
3633
3980
|
continue;
|
|
3634
3981
|
}
|
|
3982
|
+
app.unansweredPingRounds = liveness.unansweredPingRounds;
|
|
3635
3983
|
// Send PING — SDK auto-replies with PONG
|
|
3636
3984
|
this.sendToMiniapp(packageName, {
|
|
3637
3985
|
type: MiniappRequestType.PING,
|
|
@@ -3652,6 +4000,7 @@ class LocalMiniappRuntime {
|
|
|
3652
4000
|
const app = this.connectedApps.get(packageName);
|
|
3653
4001
|
if (app) {
|
|
3654
4002
|
app.lastPongAt = Date.now();
|
|
4003
|
+
app.unansweredPingRounds = 0;
|
|
3655
4004
|
this.clearForegroundProbe(packageName);
|
|
3656
4005
|
}
|
|
3657
4006
|
}
|
|
@@ -3675,7 +4024,18 @@ class LocalMiniappRuntime {
|
|
|
3675
4024
|
}
|
|
3676
4025
|
// Belt-and-suspenders: clear any remaining state
|
|
3677
4026
|
this.pendingCloudRequests.clear();
|
|
4027
|
+
const hadButtonPressSubscribers = this.getButtonPressSubscribers().length > 0;
|
|
3678
4028
|
this.streamSubscribers.clear();
|
|
4029
|
+
if (hadButtonPressSubscribers) {
|
|
4030
|
+
for (const listener of this.buttonPressSubscriberListeners) {
|
|
4031
|
+
try {
|
|
4032
|
+
listener([]);
|
|
4033
|
+
}
|
|
4034
|
+
catch (error) {
|
|
4035
|
+
console.warn(`${LOG_TAG}: button press subscriber listener threw during cleanup:`, error);
|
|
4036
|
+
}
|
|
4037
|
+
}
|
|
4038
|
+
}
|
|
3679
4039
|
this.connectedApps.clear();
|
|
3680
4040
|
for (const timerId of this.foregroundProbeTimers.values()) {
|
|
3681
4041
|
BgTimer.clearTimeout(timerId);
|