@mentra/engine 0.1.0-dev.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 +3 -2
- package/build/facades/dev.d.ts.map +1 -1
- package/build/facades/dev.js +18 -12
- package/build/facades/dev.js.map +1 -1
- package/build/facades/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/permissions.d.ts +10 -0
- package/build/facades/permissions.d.ts.map +1 -1
- package/build/facades/permissions.js +10 -0
- package/build/facades/permissions.js.map +1 -1
- package/build/facades/phoneNotifications.d.ts +0 -4
- package/build/facades/phoneNotifications.d.ts.map +1 -1
- package/build/facades/phoneNotifications.js +4 -14
- package/build/facades/phoneNotifications.js.map +1 -1
- package/build/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 +3 -2
- package/build/internal.d.ts.map +1 -1
- package/build/internal.js +2 -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 +33 -57
- package/build/services/AppRegistry.d.ts.map +1 -1
- package/build/services/AppRegistry.js +193 -144
- package/build/services/AppRegistry.js.map +1 -1
- package/build/services/AudioCloudUplink.d.ts +12 -0
- package/build/services/AudioCloudUplink.d.ts.map +1 -1
- package/build/services/AudioCloudUplink.js +22 -1
- package/build/services/AudioCloudUplink.js.map +1 -1
- package/build/services/AudioPlaybackService.d.ts +41 -2
- package/build/services/AudioPlaybackService.d.ts.map +1 -1
- package/build/services/AudioPlaybackService.js +250 -33
- package/build/services/AudioPlaybackService.js.map +1 -1
- package/build/services/BlobStore.d.ts +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 +93 -1
- package/build/services/CloudClientService.js.map +1 -1
- package/build/services/DeviceEventRouter.js +5 -5
- package/build/services/DeviceEventRouter.js.map +1 -1
- package/build/services/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 +74 -2
- package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
- package/build/services/LocalMiniappRuntime.js +785 -291
- package/build/services/LocalMiniappRuntime.js.map +1 -1
- package/build/services/LocalMiniappStorage.d.ts +32 -0
- package/build/services/LocalMiniappStorage.d.ts.map +1 -0
- package/build/services/LocalMiniappStorage.js +66 -0
- package/build/services/LocalMiniappStorage.js.map +1 -0
- package/build/services/LocalMiniappUserIdentity.d.ts +20 -0
- package/build/services/LocalMiniappUserIdentity.d.ts.map +1 -0
- package/build/services/LocalMiniappUserIdentity.js +43 -0
- package/build/services/LocalMiniappUserIdentity.js.map +1 -0
- package/build/services/LocalSttFallbackCoordinator.d.ts +9 -6
- package/build/services/LocalSttFallbackCoordinator.d.ts.map +1 -1
- package/build/services/LocalSttFallbackCoordinator.js +18 -12
- package/build/services/LocalSttFallbackCoordinator.js.map +1 -1
- package/build/services/MicStateCoordinator.d.ts +42 -1
- package/build/services/MicStateCoordinator.d.ts.map +1 -1
- package/build/services/MicStateCoordinator.js +141 -3
- package/build/services/MicStateCoordinator.js.map +1 -1
- package/build/services/MiniappLauncher.d.ts.map +1 -1
- package/build/services/MiniappLauncher.js +23 -3
- package/build/services/MiniappLauncher.js.map +1 -1
- package/build/services/MiniappLiveness.d.ts +12 -0
- package/build/services/MiniappLiveness.d.ts.map +1 -0
- package/build/services/MiniappLiveness.js +16 -0
- package/build/services/MiniappLiveness.js.map +1 -0
- package/build/services/NativeHttpResponse.d.ts +3 -0
- package/build/services/NativeHttpResponse.d.ts.map +1 -0
- package/build/services/NativeHttpResponse.js +6 -0
- package/build/services/NativeHttpResponse.js.map +1 -0
- package/build/services/OtaArtifactDownloader.d.ts +65 -0
- package/build/services/OtaArtifactDownloader.d.ts.map +1 -0
- package/build/services/OtaArtifactDownloader.js +226 -0
- package/build/services/OtaArtifactDownloader.js.map +1 -0
- package/build/services/OtaAutoChain.d.ts +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/PhoneCalendarHelpers.d.ts +32 -0
- package/build/services/PhoneCalendarHelpers.d.ts.map +1 -0
- package/build/services/PhoneCalendarHelpers.js +157 -0
- package/build/services/PhoneCalendarHelpers.js.map +1 -0
- package/build/services/PhoneCalendarService.d.ts +5 -0
- package/build/services/PhoneCalendarService.d.ts.map +1 -0
- package/build/services/PhoneCalendarService.js +45 -0
- package/build/services/PhoneCalendarService.js.map +1 -0
- package/build/services/PhoneCameraFovCoordinator.d.ts +30 -0
- package/build/services/PhoneCameraFovCoordinator.d.ts.map +1 -0
- package/build/services/PhoneCameraFovCoordinator.js +194 -0
- package/build/services/PhoneCameraFovCoordinator.js.map +1 -0
- package/build/services/PhoneNotificationsSync.d.ts.map +1 -1
- package/build/services/PhoneNotificationsSync.js +12 -10
- package/build/services/PhoneNotificationsSync.js.map +1 -1
- package/build/services/PhonePhotoCoordinator.d.ts +26 -2
- package/build/services/PhonePhotoCoordinator.d.ts.map +1 -1
- package/build/services/PhonePhotoCoordinator.js +79 -9
- package/build/services/PhonePhotoCoordinator.js.map +1 -1
- package/build/services/PhoneStreamCoordinator.d.ts +8 -1
- package/build/services/PhoneStreamCoordinator.d.ts.map +1 -1
- package/build/services/PhoneStreamCoordinator.js +125 -13
- package/build/services/PhoneStreamCoordinator.js.map +1 -1
- package/build/services/PhoneVideoCoordinator.d.ts +2 -0
- package/build/services/PhoneVideoCoordinator.d.ts.map +1 -1
- package/build/services/PhoneVideoCoordinator.js +10 -0
- package/build/services/PhoneVideoCoordinator.js.map +1 -1
- package/build/services/SceneRenderer.d.ts.map +1 -1
- package/build/services/SceneRenderer.js +15 -0
- package/build/services/SceneRenderer.js.map +1 -1
- package/build/services/SentenceTtsPipeline.d.ts +31 -0
- package/build/services/SentenceTtsPipeline.d.ts.map +1 -0
- package/build/services/SentenceTtsPipeline.js +67 -0
- package/build/services/SentenceTtsPipeline.js.map +1 -0
- package/build/services/SupportProfileSync.d.ts +22 -0
- package/build/services/SupportProfileSync.d.ts.map +1 -0
- package/build/services/SupportProfileSync.js +213 -0
- package/build/services/SupportProfileSync.js.map +1 -0
- package/build/services/TranscriptionRouting.d.ts +12 -0
- package/build/services/TranscriptionRouting.d.ts.map +1 -0
- package/build/services/TranscriptionRouting.js +24 -0
- package/build/services/TranscriptionRouting.js.map +1 -0
- package/build/services/TtsTextSanitizer.d.ts +5 -0
- package/build/services/TtsTextSanitizer.d.ts.map +1 -0
- package/build/services/TtsTextSanitizer.js +61 -0
- package/build/services/TtsTextSanitizer.js.map +1 -0
- package/build/services/WifiAdbRequest.d.ts +17 -0
- package/build/services/WifiAdbRequest.d.ts.map +1 -0
- package/build/services/WifiAdbRequest.js +35 -0
- package/build/services/WifiAdbRequest.js.map +1 -0
- package/build/services/asg/asgCameraApi.d.ts +43 -0
- package/build/services/asg/asgCameraApi.d.ts.map +1 -1
- package/build/services/asg/asgCameraApi.js +509 -26
- package/build/services/asg/asgCameraApi.js.map +1 -1
- package/build/services/asg/cameraRollExportCoordinator.d.ts +60 -0
- package/build/services/asg/cameraRollExportCoordinator.d.ts.map +1 -0
- package/build/services/asg/cameraRollExportCoordinator.js +618 -0
- package/build/services/asg/cameraRollExportCoordinator.js.map +1 -0
- package/build/services/asg/cameraRollExportLedger.d.ts +44 -0
- package/build/services/asg/cameraRollExportLedger.d.ts.map +1 -0
- package/build/services/asg/cameraRollExportLedger.js +78 -0
- package/build/services/asg/cameraRollExportLedger.js.map +1 -0
- package/build/services/asg/galleryNotices.d.ts +1 -1
- package/build/services/asg/galleryNotices.d.ts.map +1 -1
- package/build/services/asg/galleryNotices.js.map +1 -1
- package/build/services/asg/galleryPathMigration.d.ts +3 -0
- package/build/services/asg/galleryPathMigration.d.ts.map +1 -0
- package/build/services/asg/galleryPathMigration.js +26 -0
- package/build/services/asg/galleryPathMigration.js.map +1 -0
- package/build/services/asg/gallerySettingsService.d.ts +1 -0
- package/build/services/asg/gallerySettingsService.d.ts.map +1 -1
- package/build/services/asg/gallerySettingsService.js +2 -0
- package/build/services/asg/gallerySettingsService.js.map +1 -1
- package/build/services/asg/gallerySyncService.d.ts.map +1 -1
- package/build/services/asg/gallerySyncService.js +162 -56
- package/build/services/asg/gallerySyncService.js.map +1 -1
- package/build/services/asg/galleryTransferLedger.d.ts +58 -0
- package/build/services/asg/galleryTransferLedger.d.ts.map +1 -0
- package/build/services/asg/galleryTransferLedger.js +225 -0
- package/build/services/asg/galleryTransferLedger.js.map +1 -0
- package/build/services/asg/localNetworkTransport.d.ts +15 -0
- package/build/services/asg/localNetworkTransport.d.ts.map +1 -0
- package/build/services/asg/localNetworkTransport.js +144 -0
- package/build/services/asg/localNetworkTransport.js.map +1 -0
- package/build/services/asg/localStorageService.d.ts +18 -0
- package/build/services/asg/localStorageService.d.ts.map +1 -1
- package/build/services/asg/localStorageService.js +168 -19
- package/build/services/asg/localStorageService.js.map +1 -1
- package/build/services/asg/mediaProcessingQueue.d.ts +10 -0
- package/build/services/asg/mediaProcessingQueue.d.ts.map +1 -1
- package/build/services/asg/mediaProcessingQueue.js +193 -62
- package/build/services/asg/mediaProcessingQueue.js.map +1 -1
- package/build/services/audioPlaybackAssets.d.ts +8 -0
- package/build/services/audioPlaybackAssets.d.ts.map +1 -0
- package/build/services/audioPlaybackAssets.js +7 -0
- package/build/services/audioPlaybackAssets.js.map +1 -0
- package/build/services/gallerySyncClock.js +1 -1
- package/build/services/gallerySyncClock.js.map +1 -1
- package/build/services/glassesClockSync.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 +157 -17
- 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/asg.d.ts +7 -0
- package/build/types/asg.d.ts.map +1 -1
- package/build/types/asg.js.map +1 -1
- package/build/types/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/permissions/MediaLibraryPermissions.d.ts +11 -0
- package/build/utils/permissions/MediaLibraryPermissions.d.ts.map +1 -1
- package/build/utils/permissions/MediaLibraryPermissions.js +27 -4
- package/build/utils/permissions/MediaLibraryPermissions.js.map +1 -1
- package/build/utils/timers/timers.d.ts.map +1 -1
- package/build/utils/timers/timers.js +55 -18
- package/build/utils/timers/timers.js.map +1 -1
- package/package.json +25 -15
- package/src/assets/silence.wav +0 -0
- package/src/engine.ts +7 -2
- package/src/facades/dev.ts +17 -12
- 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/permissions.ts +14 -0
- package/src/facades/phoneNotifications.ts +4 -13
- package/src/index.ts +26 -2
- package/src/internal.ts +3 -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 +224 -149
- package/src/services/AudioCloudUplink.ts +23 -1
- package/src/services/AudioPlaybackService.ts +287 -38
- package/src/services/BlobStore.ts +22 -9
- package/src/services/CloudAudioSubscriptionSync.ts +13 -1
- package/src/services/CloudClientService.ts +96 -1
- package/src/services/DeviceEventRouter.ts +5 -5
- 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 +967 -346
- package/src/services/LocalMiniappStorage.ts +82 -0
- package/src/services/LocalMiniappUserIdentity.ts +46 -0
- package/src/services/LocalSttFallbackCoordinator.ts +19 -12
- package/src/services/MicStateCoordinator.ts +169 -4
- package/src/services/MiniappLauncher.ts +30 -3
- package/src/services/MiniappLiveness.ts +24 -0
- package/src/services/NativeHttpResponse.ts +6 -0
- package/src/services/OtaArtifactDownloader.ts +312 -0
- package/src/services/OtaAutoChain.ts +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/PhoneCalendarHelpers.ts +212 -0
- package/src/services/PhoneCalendarService.ts +65 -0
- package/src/services/PhoneCameraFovCoordinator.ts +210 -0
- package/src/services/PhoneNotificationsSync.ts +12 -10
- package/src/services/PhonePhotoCoordinator.ts +116 -18
- package/src/services/PhoneStreamCoordinator.ts +139 -25
- package/src/services/PhoneVideoCoordinator.ts +11 -0
- package/src/services/SceneRenderer.ts +16 -0
- package/src/services/SentenceTtsPipeline.ts +106 -0
- package/src/services/SupportProfileSync.ts +218 -0
- package/src/services/TranscriptionRouting.ts +37 -0
- package/src/services/TtsTextSanitizer.ts +66 -0
- package/src/services/WifiAdbRequest.ts +53 -0
- package/src/services/asg/asgCameraApi.ts +615 -33
- package/src/services/asg/cameraRollExportCoordinator.ts +627 -0
- package/src/services/asg/cameraRollExportLedger.ts +122 -0
- package/src/services/asg/galleryNotices.ts +1 -0
- package/src/services/asg/galleryPathMigration.ts +22 -0
- package/src/services/asg/gallerySettingsService.ts +3 -0
- package/src/services/asg/gallerySyncService.ts +184 -73
- package/src/services/asg/galleryTransferLedger.ts +312 -0
- package/src/services/asg/localNetworkTransport.ts +169 -0
- package/src/services/asg/localStorageService.ts +179 -24
- package/src/services/asg/mediaProcessingQueue.ts +209 -62
- package/src/services/audioPlaybackAssets.ts +8 -0
- package/src/services/gallerySyncClock.ts +1 -1
- package/src/services/glassesClockSync.ts +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 +179 -18
- package/src/types/applet.ts +2 -0
- package/src/types/asg.ts +7 -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/permissions/MediaLibraryPermissions.ts +35 -4
- 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
|
@@ -15,11 +15,7 @@ import type {OtaStatus} from "@mentra/bluetooth-sdk/internal"
|
|
|
15
15
|
import GlobalEventEmitter from "../utils/GlobalEventEmitter"
|
|
16
16
|
import {useGlassesStore} from "../stores/glasses"
|
|
17
17
|
import {handleOtaClockSkewFromGlasses} from "./glassesClockSync"
|
|
18
|
-
import {
|
|
19
|
-
legacyOtaProgressFromOtaStatusEvent,
|
|
20
|
-
normalizeOtaStatusEvent,
|
|
21
|
-
otaStatusFromNormalized,
|
|
22
|
-
} from "./otaLegacyMapping"
|
|
18
|
+
import {legacyOtaProgressFromOtaStatusEvent, normalizeOtaStatusEvent, otaStatusFromNormalized} from "./otaLegacyMapping"
|
|
23
19
|
|
|
24
20
|
let subs: Array<{remove: () => void}> = []
|
|
25
21
|
|
|
@@ -40,6 +36,16 @@ export function startOtaService(): void {
|
|
|
40
36
|
}),
|
|
41
37
|
)
|
|
42
38
|
|
|
39
|
+
// The glasses process restarted under a live BLE link (sid changed in
|
|
40
|
+
// glasses_ready/version_info_1). The physical connection never dropped, so this is
|
|
41
|
+
// the ONLY reconnect-edge signal the OTA coordinator gets after an APK install.
|
|
42
|
+
subs.push(
|
|
43
|
+
BluetoothSdk.addListener("glasses_session_changed", (event) => {
|
|
44
|
+
console.log(`[OTA] glasses_session_changed ${event.previous_sid || "<none>"} -> ${event.sid}`)
|
|
45
|
+
GlobalEventEmitter.emit("glasses_session_changed", {previousSid: event.previous_sid, sid: event.sid})
|
|
46
|
+
}),
|
|
47
|
+
)
|
|
48
|
+
|
|
43
49
|
// Install progress / lifecycle. Updates both the new (otaStatus) and the legacy
|
|
44
50
|
// (otaProgress) store shapes; auto-fixes clock skew on the relevant failures.
|
|
45
51
|
subs.push(
|
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
import BluetoothSdk from "@mentra/bluetooth-sdk/internal"
|
|
2
2
|
import type {OtaUpdateInfo} from "@mentra/bluetooth-sdk/internal"
|
|
3
3
|
import {getGlassesSystemTimeMs, isGlassesConnected, useGlassesStore, waitForGlassesState} from "../stores/glasses"
|
|
4
|
+
import {SETTINGS, useSettingsStore} from "../stores/settings"
|
|
4
5
|
import {maybeFixGlassesClockFromVersionInfo} from "./glassesClockSync"
|
|
5
|
-
import {
|
|
6
|
+
import {resolveOtaManifestUrl} from "./otaManifestUrl"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Phone-side mirror of ASG's `DOWNGRADE_FLOOR_VERSION_CODE`. Set to the versionCode of the first
|
|
10
|
+
* release shipping the shared media root (the first downgrade-safe build). A non-positive value
|
|
11
|
+
* means downgrades are disabled, so the phone must NOT offer them — offering a downgrade the
|
|
12
|
+
* glasses' `DowngradeGate` will refuse leaves the version-change session with no install and the
|
|
13
|
+
* glasses reporting a bare "complete", which would otherwise present as false success. Keep this
|
|
14
|
+
* in lockstep with the ASG/recovery constant at release time.
|
|
15
|
+
*/
|
|
16
|
+
export const DOWNGRADE_FLOOR_VERSION_CODE = 0
|
|
6
17
|
|
|
7
18
|
export interface VersionInfo {
|
|
8
19
|
versionCode: number
|
|
@@ -18,11 +29,13 @@ export interface MtkPatch {
|
|
|
18
29
|
start_firmware: string
|
|
19
30
|
end_firmware: string
|
|
20
31
|
url: string
|
|
32
|
+
sha256?: string
|
|
21
33
|
}
|
|
22
34
|
|
|
23
35
|
export interface BesFirmware {
|
|
24
36
|
version: string
|
|
25
37
|
url: string
|
|
38
|
+
sha256?: string
|
|
26
39
|
}
|
|
27
40
|
|
|
28
41
|
export interface VersionJson {
|
|
@@ -46,9 +59,23 @@ export interface OtaCheckResult {
|
|
|
46
59
|
updates: string[]
|
|
47
60
|
mtkPatch: MtkPatch | null
|
|
48
61
|
besVersion: string | null
|
|
62
|
+
/** True when the pending APK step installs an OLDER build than the glasses currently run. */
|
|
63
|
+
isApkDowngrade: boolean
|
|
64
|
+
/**
|
|
65
|
+
* Raw manifest JSON (stringified) this check actually fetched, or null when the
|
|
66
|
+
* fetch failed. Lets change-watchers baseline on exactly what the check saw,
|
|
67
|
+
* instead of racing it with a second fetch of the same URL.
|
|
68
|
+
*/
|
|
69
|
+
manifestBody: string | null
|
|
70
|
+
/**
|
|
71
|
+
* Why the check failed, when it did. "network" is retryable (connection/server
|
|
72
|
+
* trouble); "pin_unavailable" is permanent for this app build (manifest 404/gone,
|
|
73
|
+
* unparseable, or carrying no valid ASG pin) — the remedy is updating the app.
|
|
74
|
+
*/
|
|
75
|
+
checkFailureReason?: "network" | "pin_unavailable"
|
|
49
76
|
}
|
|
50
77
|
|
|
51
|
-
export type OtaCheckSkippedReason = "disconnected" | "missing_build"
|
|
78
|
+
export type OtaCheckSkippedReason = "disconnected" | "missing_build" | "dev_build"
|
|
52
79
|
|
|
53
80
|
export interface OtaCheckCurrentGlassesResult extends OtaCheckResult {
|
|
54
81
|
updateInfo: OtaUpdateInfo | null
|
|
@@ -66,6 +93,8 @@ export interface OtaCheckCurrentGlassesOptions {
|
|
|
66
93
|
waitForMtkVersionMs?: number
|
|
67
94
|
refreshVersionInfo?: boolean
|
|
68
95
|
fixClockBeforeCheck?: boolean
|
|
96
|
+
/** Downgrade floor override (defaults to DOWNGRADE_FLOOR_VERSION_CODE); for tests. */
|
|
97
|
+
floorVersionCode?: number
|
|
69
98
|
}
|
|
70
99
|
|
|
71
100
|
function emptyCheckResult(skippedReason?: OtaCheckSkippedReason): OtaCheckCurrentGlassesResult {
|
|
@@ -76,6 +105,8 @@ function emptyCheckResult(skippedReason?: OtaCheckSkippedReason): OtaCheckCurren
|
|
|
76
105
|
updates: [],
|
|
77
106
|
mtkPatch: null,
|
|
78
107
|
besVersion: null,
|
|
108
|
+
isApkDowngrade: false,
|
|
109
|
+
manifestBody: null,
|
|
79
110
|
updateInfo: null,
|
|
80
111
|
isRequired: true,
|
|
81
112
|
skippedReason,
|
|
@@ -86,46 +117,92 @@ function glassesConnectedNow(): boolean {
|
|
|
86
117
|
return isGlassesConnected(useGlassesStore.getState().connection)
|
|
87
118
|
}
|
|
88
119
|
|
|
89
|
-
|
|
120
|
+
type ManifestFetchResult = {json: VersionJson | null; failureReason?: "network" | "pin_unavailable"}
|
|
121
|
+
|
|
122
|
+
async function fetchVersionInfoDetailed(url: string): Promise<ManifestFetchResult> {
|
|
90
123
|
try {
|
|
91
124
|
const response = await fetch(url)
|
|
92
125
|
if (!response.ok) {
|
|
93
126
|
console.error("Failed to fetch version info:", response.status)
|
|
94
|
-
|
|
127
|
+
// 4xx: the manifest does not exist (or is gone) — permanent for this app
|
|
128
|
+
// build, retrying cannot help. Everything else is transient server/network
|
|
129
|
+
// trouble and retryable.
|
|
130
|
+
return {json: null, failureReason: response.status >= 400 && response.status < 500 ? "pin_unavailable" : "network"}
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
return {json: await response.json()}
|
|
134
|
+
} catch (parseError) {
|
|
135
|
+
console.error("OTA: manifest is not valid JSON:", parseError)
|
|
136
|
+
return {json: null, failureReason: "pin_unavailable"}
|
|
95
137
|
}
|
|
96
|
-
return await response.json()
|
|
97
138
|
} catch (error) {
|
|
98
139
|
console.error("OTA: Error fetching version info:", error)
|
|
99
|
-
return null
|
|
140
|
+
return {json: null, failureReason: "network"}
|
|
100
141
|
}
|
|
101
142
|
}
|
|
102
143
|
|
|
144
|
+
export async function fetchVersionInfo(url: string): Promise<VersionJson | null> {
|
|
145
|
+
return (await fetchVersionInfoDetailed(url)).json
|
|
146
|
+
}
|
|
147
|
+
|
|
103
148
|
export function checkVersionUpdateAvailable(
|
|
104
149
|
currentBuildNumber: string | undefined,
|
|
105
150
|
versionJson: VersionJson | null,
|
|
151
|
+
floorVersionCode: number = DOWNGRADE_FLOOR_VERSION_CODE,
|
|
106
152
|
): boolean {
|
|
153
|
+
return getApkUpdateDirection(currentBuildNumber, versionJson, floorVersionCode) !== null
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Direction of the pending APK change, or null when the glasses already match the manifest.
|
|
158
|
+
* Every apps-shaped manifest the phone drives is an exact pin, so both directions are
|
|
159
|
+
* actionable (mirroring the glasses-side `DowngradeGate`); the ancient top-level manifest
|
|
160
|
+
* shape stays upgrade-only, and zeroed rescue pins are never actionable.
|
|
161
|
+
*/
|
|
162
|
+
export function getApkUpdateDirection(
|
|
163
|
+
currentBuildNumber: string | undefined,
|
|
164
|
+
versionJson: VersionJson | null,
|
|
165
|
+
floorVersionCode: number = DOWNGRADE_FLOOR_VERSION_CODE,
|
|
166
|
+
): "upgrade" | "downgrade" | null {
|
|
107
167
|
if (!currentBuildNumber || !versionJson) {
|
|
108
|
-
return
|
|
168
|
+
return null
|
|
109
169
|
}
|
|
110
170
|
|
|
111
171
|
const currentVersion = parseInt(currentBuildNumber, 10)
|
|
112
172
|
if (isNaN(currentVersion)) {
|
|
113
|
-
return
|
|
173
|
+
return null
|
|
114
174
|
}
|
|
115
175
|
|
|
116
176
|
let serverVersion: number | undefined
|
|
177
|
+
let exactPin = false
|
|
117
178
|
|
|
118
|
-
|
|
119
|
-
|
|
179
|
+
const appEntry = versionJson.apps?.["com.mentra.asg_client"]
|
|
180
|
+
if (appEntry) {
|
|
181
|
+
serverVersion = appEntry.versionCode
|
|
182
|
+
exactPin = true
|
|
120
183
|
} else if (versionJson.versionCode) {
|
|
121
184
|
serverVersion = versionJson.versionCode
|
|
122
185
|
}
|
|
123
186
|
|
|
124
187
|
if (!serverVersion || isNaN(serverVersion)) {
|
|
125
|
-
return
|
|
188
|
+
return null
|
|
126
189
|
}
|
|
127
190
|
|
|
128
|
-
|
|
191
|
+
if (serverVersion > currentVersion) {
|
|
192
|
+
return "upgrade"
|
|
193
|
+
}
|
|
194
|
+
// Downgrades require an exact pin AND a target at/above the enabled floor. A non-positive floor
|
|
195
|
+
// disables downgrades entirely, matching ASG's fail-closed DowngradeGate — so the phone never
|
|
196
|
+
// offers what the glasses would refuse.
|
|
197
|
+
if (
|
|
198
|
+
serverVersion < currentVersion &&
|
|
199
|
+
exactPin &&
|
|
200
|
+
floorVersionCode > 0 &&
|
|
201
|
+
serverVersion >= floorVersionCode
|
|
202
|
+
) {
|
|
203
|
+
return "downgrade"
|
|
204
|
+
}
|
|
205
|
+
return null
|
|
129
206
|
}
|
|
130
207
|
|
|
131
208
|
export function getLatestVersionInfo(versionJson: VersionJson | null): VersionInfo | null {
|
|
@@ -184,7 +261,7 @@ export function checkBesUpdate(besFirmware: BesFirmware | undefined, currentVers
|
|
|
184
261
|
return compareVersions(besFirmware.version, currentVersion) > 0
|
|
185
262
|
}
|
|
186
263
|
|
|
187
|
-
function compareVersions(version1: string, version2: string): number {
|
|
264
|
+
export function compareVersions(version1: string, version2: string): number {
|
|
188
265
|
if (version1.includes(".") && version2.includes(".")) {
|
|
189
266
|
const parts1 = version1.split(".")
|
|
190
267
|
const parts2 = version2.split(".")
|
|
@@ -208,14 +285,62 @@ export async function checkForOtaUpdate(
|
|
|
208
285
|
currentBuildNumber: string,
|
|
209
286
|
currentMtkVersion?: string,
|
|
210
287
|
currentBesVersion?: string,
|
|
288
|
+
floorVersionCode: number = DOWNGRADE_FLOOR_VERSION_CODE,
|
|
211
289
|
): Promise<OtaCheckResult> {
|
|
212
290
|
try {
|
|
213
291
|
console.log("OTA: Checking for OTA update - URL: " + otaVersionUrl + ", current build: " + currentBuildNumber)
|
|
214
|
-
const
|
|
292
|
+
const fetched = await fetchVersionInfoDetailed(otaVersionUrl)
|
|
293
|
+
const versionJson = fetched.json
|
|
294
|
+
if (!versionJson) {
|
|
295
|
+
return {
|
|
296
|
+
hasCheckCompleted: false,
|
|
297
|
+
updateAvailable: false,
|
|
298
|
+
latestVersionInfo: null,
|
|
299
|
+
updates: [],
|
|
300
|
+
mtkPatch: null,
|
|
301
|
+
besVersion: null,
|
|
302
|
+
isApkDowngrade: false,
|
|
303
|
+
manifestBody: null,
|
|
304
|
+
checkFailureReason: fetched.failureReason ?? "network",
|
|
305
|
+
}
|
|
306
|
+
}
|
|
215
307
|
const latestVersionInfo = getLatestVersionInfo(versionJson)
|
|
216
308
|
|
|
217
|
-
|
|
218
|
-
|
|
309
|
+
// A manifest whose ASG pin is missing/zeroed cannot verify anything for modern
|
|
310
|
+
// glasses (zeroed pins are only legitimate in the frozen legacy rescue manifests
|
|
311
|
+
// that pre-39 glasses are checked against). Report the check as FAILED rather
|
|
312
|
+
// than silently completing as "up to date" — an unverifiable state must never
|
|
313
|
+
// present as a verified one.
|
|
314
|
+
const currentVersionNumber = parseInt(currentBuildNumber, 10)
|
|
315
|
+
const pinnedEntry = versionJson?.apps?.["com.mentra.asg_client"]
|
|
316
|
+
const pinnedVersion = pinnedEntry?.versionCode ?? versionJson?.versionCode
|
|
317
|
+
if (
|
|
318
|
+
versionJson &&
|
|
319
|
+
Number.isFinite(currentVersionNumber) &&
|
|
320
|
+
currentVersionNumber >= 39 &&
|
|
321
|
+
!(typeof pinnedVersion === "number" && pinnedVersion > 0)
|
|
322
|
+
) {
|
|
323
|
+
console.error(
|
|
324
|
+
`OTA: manifest at ${otaVersionUrl} has no valid ASG pin (versionCode=${String(pinnedVersion)}) - treating check as failed`,
|
|
325
|
+
)
|
|
326
|
+
return {
|
|
327
|
+
hasCheckCompleted: false,
|
|
328
|
+
updateAvailable: false,
|
|
329
|
+
latestVersionInfo: null,
|
|
330
|
+
updates: [],
|
|
331
|
+
mtkPatch: null,
|
|
332
|
+
besVersion: null,
|
|
333
|
+
isApkDowngrade: false,
|
|
334
|
+
manifestBody: null,
|
|
335
|
+
checkFailureReason: "pin_unavailable",
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const apkDirection = getApkUpdateDirection(currentBuildNumber, versionJson, floorVersionCode)
|
|
340
|
+
const apkUpdateAvailable = apkDirection !== null
|
|
341
|
+
console.log(
|
|
342
|
+
`OTA: APK update available: ${apkUpdateAvailable} (current: ${currentBuildNumber}, direction: ${apkDirection ?? "none"})`,
|
|
343
|
+
)
|
|
219
344
|
|
|
220
345
|
const mtkPatch = findMatchingMtkPatch(versionJson?.mtk_patches, currentMtkVersion)
|
|
221
346
|
const mtkUpdateAvailable = mtkPatch !== null
|
|
@@ -243,6 +368,8 @@ export async function checkForOtaUpdate(
|
|
|
243
368
|
updates,
|
|
244
369
|
mtkPatch,
|
|
245
370
|
besVersion: versionJson?.bes_firmware?.version || null,
|
|
371
|
+
isApkDowngrade: apkDirection === "downgrade",
|
|
372
|
+
manifestBody: versionJson ? JSON.stringify(versionJson) : null,
|
|
246
373
|
}
|
|
247
374
|
} catch (error) {
|
|
248
375
|
console.error("Error checking for OTA update:", error)
|
|
@@ -253,6 +380,9 @@ export async function checkForOtaUpdate(
|
|
|
253
380
|
updates: [],
|
|
254
381
|
mtkPatch: null,
|
|
255
382
|
besVersion: null,
|
|
383
|
+
isApkDowngrade: false,
|
|
384
|
+
manifestBody: null,
|
|
385
|
+
checkFailureReason: "network",
|
|
256
386
|
}
|
|
257
387
|
}
|
|
258
388
|
}
|
|
@@ -266,12 +396,25 @@ export async function checkCurrentGlassesForUpdate(
|
|
|
266
396
|
waitForMtkVersionMs = 2000,
|
|
267
397
|
refreshVersionInfo = true,
|
|
268
398
|
fixClockBeforeCheck = true,
|
|
399
|
+
floorVersionCode = DOWNGRADE_FLOOR_VERSION_CODE,
|
|
269
400
|
} = options
|
|
270
401
|
|
|
271
402
|
if (!glassesConnectedNow()) {
|
|
272
403
|
return emptyCheckResult("disconnected")
|
|
273
404
|
}
|
|
274
405
|
|
|
406
|
+
const embeddedManifestUrl = process.env.EXPO_PUBLIC_ASG_OTA_VERSION_URL?.trim()
|
|
407
|
+
const superMode = useSettingsStore.getState().getSetting(SETTINGS.super_mode.key)
|
|
408
|
+
const overrideUrl = useSettingsStore.getState().getSetting(SETTINGS.ota_version_url.key)
|
|
409
|
+
const overrideActive = superMode && typeof overrideUrl === "string" && overrideUrl.trim() !== ""
|
|
410
|
+
if (!embeddedManifestUrl && !overrideActive) {
|
|
411
|
+
console.log("OTA: check skipped - mobile app has no embedded OTA manifest pin")
|
|
412
|
+
return emptyCheckResult("dev_build")
|
|
413
|
+
}
|
|
414
|
+
if (!embeddedManifestUrl) {
|
|
415
|
+
console.log("OTA: no embedded manifest pin, but super-mode manifest override is active - checking anyway")
|
|
416
|
+
}
|
|
417
|
+
|
|
275
418
|
if (refreshVersionInfo) {
|
|
276
419
|
void BluetoothSdk.requestVersionInfo().catch((error) => {
|
|
277
420
|
console.warn("OTA: Failed to request version_info from glasses:", error)
|
|
@@ -284,7 +427,10 @@ export async function checkCurrentGlassesForUpdate(
|
|
|
284
427
|
buildNumber = useGlassesStore.getState().buildNumber
|
|
285
428
|
}
|
|
286
429
|
|
|
287
|
-
if (!buildNumber) {
|
|
430
|
+
if (!buildNumber || !(parseInt(buildNumber, 10) > 0)) {
|
|
431
|
+
// Covers absent, non-numeric, and zero build numbers: a glasses version we
|
|
432
|
+
// cannot parse means nothing is verifiable, so skip rather than ghost an
|
|
433
|
+
// "up to date" result from a comparison that never really ran.
|
|
288
434
|
return emptyCheckResult("missing_build")
|
|
289
435
|
}
|
|
290
436
|
|
|
@@ -325,8 +471,8 @@ export async function checkCurrentGlassesForUpdate(
|
|
|
325
471
|
})
|
|
326
472
|
}
|
|
327
473
|
|
|
328
|
-
const manifestUrl =
|
|
329
|
-
const result = await checkForOtaUpdate(manifestUrl, buildNumber, mtkFirmwareVersion, besFirmwareVersion)
|
|
474
|
+
const manifestUrl = resolveOtaManifestUrl(useGlassesStore.getState().otaVersionUrl, buildNumber)
|
|
475
|
+
const result = await checkForOtaUpdate(manifestUrl, buildNumber, mtkFirmwareVersion, besFirmwareVersion, floorVersionCode)
|
|
330
476
|
|
|
331
477
|
if (!result.hasCheckCompleted) {
|
|
332
478
|
return {
|
|
@@ -360,6 +506,7 @@ export async function checkCurrentGlassesForUpdate(
|
|
|
360
506
|
)
|
|
361
507
|
}
|
|
362
508
|
const updateAvailable = filteredUpdates.length > 0 && !!result.latestVersionInfo
|
|
509
|
+
const isApkDowngrade = result.isApkDowngrade && filteredUpdates.includes("apk")
|
|
363
510
|
const updateInfo = updateAvailable
|
|
364
511
|
? {
|
|
365
512
|
available: true,
|
|
@@ -367,6 +514,8 @@ export async function checkCurrentGlassesForUpdate(
|
|
|
367
514
|
versionName: result.latestVersionInfo?.versionName || "",
|
|
368
515
|
updates: filteredUpdates,
|
|
369
516
|
totalSize: 0,
|
|
517
|
+
...(filteredUpdates.includes("bes") && result.besVersion ? {besVersion: result.besVersion} : {}),
|
|
518
|
+
isDowngrade: isApkDowngrade,
|
|
370
519
|
}
|
|
371
520
|
: null
|
|
372
521
|
|
|
@@ -376,8 +525,19 @@ export async function checkCurrentGlassesForUpdate(
|
|
|
376
525
|
...result,
|
|
377
526
|
updateAvailable,
|
|
378
527
|
updates: filteredUpdates,
|
|
528
|
+
isApkDowngrade,
|
|
379
529
|
updateInfo,
|
|
380
|
-
|
|
530
|
+
// Required-update policy: an EXPLICIT isRequired in the manifest is the
|
|
531
|
+
// release-engineering escape hatch and wins in both directions. When absent,
|
|
532
|
+
// upgrades keep the historical forced default, but downgrades are always
|
|
533
|
+
// skippable — a version change that resets glasses settings must be
|
|
534
|
+
// declinable unless someone explicitly forced it.
|
|
535
|
+
isRequired:
|
|
536
|
+
result.latestVersionInfo?.isRequired === true
|
|
537
|
+
? true
|
|
538
|
+
: result.latestVersionInfo?.isRequired === false
|
|
539
|
+
? false
|
|
540
|
+
: !isApkDowngrade,
|
|
381
541
|
manifestUrl,
|
|
382
542
|
buildNumber,
|
|
383
543
|
mtkFirmwareVersion,
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import {MiniappErrorCode, type CalendarEvent} from "@mentra/miniapp"
|
|
2
|
+
|
|
3
|
+
const DEFAULT_LIMIT = 50
|
|
4
|
+
const MAX_LIMIT = 100
|
|
5
|
+
const MAX_RANGE_MS = 31 * 24 * 60 * 60 * 1000
|
|
6
|
+
|
|
7
|
+
export interface CalendarListRequest {
|
|
8
|
+
startsAt?: unknown
|
|
9
|
+
endsAt?: unknown
|
|
10
|
+
limit?: unknown
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class PhoneCalendarError extends Error {
|
|
14
|
+
constructor(
|
|
15
|
+
public readonly code: MiniappErrorCode,
|
|
16
|
+
message: string,
|
|
17
|
+
) {
|
|
18
|
+
super(message)
|
|
19
|
+
this.name = "PhoneCalendarError"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ValidatedCalendarListRequest {
|
|
24
|
+
startsAt: Date
|
|
25
|
+
endsAt: Date
|
|
26
|
+
limit: number
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface RawCalendarEvent {
|
|
30
|
+
id?: string
|
|
31
|
+
calendarId?: string
|
|
32
|
+
title?: string | null
|
|
33
|
+
startDate: string | Date
|
|
34
|
+
endDate: string | Date
|
|
35
|
+
timeZone?: string | null
|
|
36
|
+
allDay?: boolean
|
|
37
|
+
location?: string | null
|
|
38
|
+
notes?: string | null
|
|
39
|
+
url?: string | null
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function validateCalendarListRequest(request: CalendarListRequest): ValidatedCalendarListRequest {
|
|
43
|
+
const startsAt = parseDate(request.startsAt, "startsAt")
|
|
44
|
+
const endsAt = parseDate(request.endsAt, "endsAt")
|
|
45
|
+
if (endsAt.getTime() <= startsAt.getTime()) {
|
|
46
|
+
throw new PhoneCalendarError(MiniappErrorCode.INVALID_ARGUMENT, "endsAt must be after startsAt")
|
|
47
|
+
}
|
|
48
|
+
if (endsAt.getTime() - startsAt.getTime() > MAX_RANGE_MS) {
|
|
49
|
+
throw new PhoneCalendarError(MiniappErrorCode.INVALID_ARGUMENT, "Calendar query range may not exceed 31 days")
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const limit = request.limit === undefined ? DEFAULT_LIMIT : request.limit
|
|
53
|
+
if (typeof limit !== "number" || !Number.isInteger(limit) || limit < 1 || limit > MAX_LIMIT) {
|
|
54
|
+
throw new PhoneCalendarError(
|
|
55
|
+
MiniappErrorCode.INVALID_ARGUMENT,
|
|
56
|
+
`limit must be an integer between 1 and ${MAX_LIMIT}`,
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
return {startsAt, endsAt, limit}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function parseDate(value: unknown, field: string): Date {
|
|
63
|
+
if (typeof value !== "string") {
|
|
64
|
+
throw new PhoneCalendarError(MiniappErrorCode.INVALID_ARGUMENT, `${field} must be an ISO 8601 string`)
|
|
65
|
+
}
|
|
66
|
+
const date = new Date(value)
|
|
67
|
+
if (Number.isNaN(date.getTime())) {
|
|
68
|
+
throw new PhoneCalendarError(MiniappErrorCode.INVALID_ARGUMENT, `${field} must be a valid ISO 8601 string`)
|
|
69
|
+
}
|
|
70
|
+
return date
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function extractHttpsLinks(...values: Array<string | null | undefined>): string[] {
|
|
74
|
+
const links = new Set<string>()
|
|
75
|
+
for (const value of values) {
|
|
76
|
+
for (const match of value?.match(/https:\/\/[^\s<>"']+/gi) ?? []) {
|
|
77
|
+
let link = match
|
|
78
|
+
while (".,;:!?)]}".includes(link[link.length - 1] ?? "")) link = link.slice(0, -1)
|
|
79
|
+
if (link) links.add(link)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return [...links]
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
interface CalendarDateParts {
|
|
86
|
+
year: number
|
|
87
|
+
month: number
|
|
88
|
+
day: number
|
|
89
|
+
hour: number
|
|
90
|
+
minute: number
|
|
91
|
+
second: number
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function localDateParts(date: Date): CalendarDateParts {
|
|
95
|
+
return {
|
|
96
|
+
year: date.getFullYear(),
|
|
97
|
+
month: date.getMonth() + 1,
|
|
98
|
+
day: date.getDate(),
|
|
99
|
+
hour: date.getHours(),
|
|
100
|
+
minute: date.getMinutes(),
|
|
101
|
+
second: date.getSeconds(),
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function datePartsInTimeZone(date: Date, timeZone: string): CalendarDateParts | undefined {
|
|
106
|
+
try {
|
|
107
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
108
|
+
timeZone,
|
|
109
|
+
year: "numeric",
|
|
110
|
+
month: "2-digit",
|
|
111
|
+
day: "2-digit",
|
|
112
|
+
hour: "2-digit",
|
|
113
|
+
minute: "2-digit",
|
|
114
|
+
second: "2-digit",
|
|
115
|
+
hourCycle: "h23",
|
|
116
|
+
}).formatToParts(date)
|
|
117
|
+
const value = (type: Intl.DateTimeFormatPartTypes) => Number(parts.find((part) => part.type === type)?.value)
|
|
118
|
+
return {
|
|
119
|
+
year: value("year"),
|
|
120
|
+
month: value("month"),
|
|
121
|
+
day: value("day"),
|
|
122
|
+
hour: value("hour"),
|
|
123
|
+
minute: value("minute"),
|
|
124
|
+
second: value("second"),
|
|
125
|
+
}
|
|
126
|
+
} catch {
|
|
127
|
+
return undefined
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function timeZoneOffsetMinutes(date: Date, timeZone: string): number | undefined {
|
|
132
|
+
const parts = datePartsInTimeZone(date, timeZone)
|
|
133
|
+
if (!parts) return undefined
|
|
134
|
+
const representedAsUtc = Date.UTC(
|
|
135
|
+
parts.year,
|
|
136
|
+
parts.month - 1,
|
|
137
|
+
parts.day,
|
|
138
|
+
parts.hour,
|
|
139
|
+
parts.minute,
|
|
140
|
+
parts.second,
|
|
141
|
+
date.getUTCMilliseconds(),
|
|
142
|
+
)
|
|
143
|
+
return Math.round((representedAsUtc - date.getTime()) / 60_000)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function pad(value: number, length = 2): string {
|
|
147
|
+
return String(value).padStart(length, "0")
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function formatOffset(offsetMinutes: number): string {
|
|
151
|
+
if (offsetMinutes === 0) return "Z"
|
|
152
|
+
const sign = offsetMinutes < 0 ? "-" : "+"
|
|
153
|
+
const absolute = Math.abs(offsetMinutes)
|
|
154
|
+
return `${sign}${pad(Math.floor(absolute / 60))}:${pad(absolute % 60)}`
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function formatAllDayDate(value: string | Date, timeZone?: string | null): string {
|
|
158
|
+
const dateOnly = typeof value === "string" ? /^(\d{4})-(\d{2})-(\d{2})$/.exec(value) : null
|
|
159
|
+
if (dateOnly) {
|
|
160
|
+
const [, year, month, day] = dateOnly
|
|
161
|
+
const localMidnight = new Date(Number(year), Number(month) - 1, Number(day))
|
|
162
|
+
let offsetMinutes = -localMidnight.getTimezoneOffset()
|
|
163
|
+
if (timeZone) {
|
|
164
|
+
// Resolve the offset at midnight in the event timezone. Repeating once
|
|
165
|
+
// accounts for the initial UTC guess landing across a DST boundary.
|
|
166
|
+
const wallTime = Date.UTC(Number(year), Number(month) - 1, Number(day))
|
|
167
|
+
let instant = new Date(wallTime)
|
|
168
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
169
|
+
const offset = timeZoneOffsetMinutes(instant, timeZone)
|
|
170
|
+
if (offset === undefined) break
|
|
171
|
+
offsetMinutes = offset
|
|
172
|
+
instant = new Date(wallTime - offset * 60_000)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return `${year}-${month}-${day}T00:00:00.000${formatOffset(offsetMinutes)}`
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const date = new Date(value)
|
|
179
|
+
const zonedParts = timeZone ? datePartsInTimeZone(date, timeZone) : undefined
|
|
180
|
+
const parts = zonedParts ?? localDateParts(date)
|
|
181
|
+
const offsetMinutes =
|
|
182
|
+
timeZone && zonedParts
|
|
183
|
+
? (timeZoneOffsetMinutes(date, timeZone) ?? -date.getTimezoneOffset())
|
|
184
|
+
: -date.getTimezoneOffset()
|
|
185
|
+
return `${pad(parts.year, 4)}-${pad(parts.month)}-${pad(parts.day)}T${pad(parts.hour)}:${pad(parts.minute)}:${pad(parts.second)}.${pad(date.getUTCMilliseconds(), 3)}${formatOffset(offsetMinutes)}`
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function normalizeCalendarEvent(event: RawCalendarEvent): CalendarEvent {
|
|
189
|
+
const startsAt = event.allDay
|
|
190
|
+
? formatAllDayDate(event.startDate, event.timeZone)
|
|
191
|
+
: new Date(event.startDate).toISOString()
|
|
192
|
+
const endsAt = event.allDay ? formatAllDayDate(event.endDate, event.timeZone) : new Date(event.endDate).toISOString()
|
|
193
|
+
const calendarId = event.calendarId ?? ""
|
|
194
|
+
const eventId = event.id ?? ""
|
|
195
|
+
return {
|
|
196
|
+
id: `${calendarId}:${eventId}:${startsAt}`,
|
|
197
|
+
calendarId,
|
|
198
|
+
title: event.title ?? "",
|
|
199
|
+
startsAt,
|
|
200
|
+
endsAt,
|
|
201
|
+
...(event.timeZone ? {timezone: event.timeZone} : {}),
|
|
202
|
+
allDay: !!event.allDay,
|
|
203
|
+
...(event.location ? {location: event.location} : {}),
|
|
204
|
+
...(event.notes ? {notes: event.notes} : {}),
|
|
205
|
+
...(event.url ? {url: event.url} : {}),
|
|
206
|
+
links: extractHttpsLinks(event.url, event.location, event.notes),
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function calendarEventOverlapsWindow(event: CalendarEvent, startsAt: Date, endsAt: Date): boolean {
|
|
211
|
+
return Date.parse(event.endsAt) > startsAt.getTime() && Date.parse(event.startsAt) < endsAt.getTime()
|
|
212
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as Calendar from "expo-calendar"
|
|
2
|
+
import {Platform} from "react-native"
|
|
3
|
+
import {check, PERMISSIONS, RESULTS} from "react-native-permissions"
|
|
4
|
+
|
|
5
|
+
import {MiniappErrorCode, type CalendarListResult} from "@mentra/miniapp"
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
calendarEventOverlapsWindow,
|
|
9
|
+
type CalendarListRequest,
|
|
10
|
+
normalizeCalendarEvent,
|
|
11
|
+
PhoneCalendarError,
|
|
12
|
+
validateCalendarListRequest,
|
|
13
|
+
} from "./PhoneCalendarHelpers"
|
|
14
|
+
|
|
15
|
+
export {PhoneCalendarError} from "./PhoneCalendarHelpers"
|
|
16
|
+
|
|
17
|
+
type CalendarPermissionStatus = Awaited<
|
|
18
|
+
ReturnType<typeof Calendar.getCalendarPermissionsAsync>
|
|
19
|
+
>["status"]
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* expo-calendar reports anything below EventKit full access as not granted,
|
|
23
|
+
* so distinguish the states the user can actually fix. "Reopen the miniapp"
|
|
24
|
+
* is only true while the permission is undetermined — once iOS has resolved
|
|
25
|
+
* it (denied or write-only/limited), only Settings can change it.
|
|
26
|
+
*/
|
|
27
|
+
async function calendarPermissionMessage(status: CalendarPermissionStatus): Promise<string> {
|
|
28
|
+
if (status === "undetermined") {
|
|
29
|
+
return "Calendar permission has not been granted yet. Reopen the miniapp and allow calendar access."
|
|
30
|
+
}
|
|
31
|
+
if (Platform.OS === "ios") {
|
|
32
|
+
const writeOnly = await check(PERMISSIONS.IOS.CALENDARS_WRITE_ONLY).catch(() => null)
|
|
33
|
+
if (writeOnly === RESULTS.GRANTED || writeOnly === RESULTS.LIMITED) {
|
|
34
|
+
return 'Calendar access is limited to write-only. Enable "Full Access" for Mentra in Settings › Privacy & Security › Calendars.'
|
|
35
|
+
}
|
|
36
|
+
return "Calendar access is denied. Enable it for Mentra in Settings › Privacy & Security › Calendars."
|
|
37
|
+
}
|
|
38
|
+
return "Calendar access is denied. Enable the Calendar permission for Mentra in your phone's app settings."
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function listPhoneCalendarEvents(request: CalendarListRequest): Promise<CalendarListResult> {
|
|
42
|
+
const {startsAt, endsAt, limit} = validateCalendarListRequest(request)
|
|
43
|
+
const permission = await Calendar.getCalendarPermissionsAsync()
|
|
44
|
+
if (permission.status !== "granted") {
|
|
45
|
+
throw new PhoneCalendarError(MiniappErrorCode.PERMISSION_DENIED, await calendarPermissionMessage(permission.status))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const calendars = await Calendar.getCalendarsAsync(Calendar.EntityTypes.EVENT)
|
|
49
|
+
if (calendars.length === 0) return {events: [], truncated: false}
|
|
50
|
+
|
|
51
|
+
const rawEvents = await Calendar.getEventsAsync(
|
|
52
|
+
calendars.map((calendar) => calendar.id),
|
|
53
|
+
startsAt,
|
|
54
|
+
endsAt,
|
|
55
|
+
)
|
|
56
|
+
const events = rawEvents
|
|
57
|
+
.map(normalizeCalendarEvent)
|
|
58
|
+
.filter((event) => calendarEventOverlapsWindow(event, startsAt, endsAt))
|
|
59
|
+
.sort((a, b) => Date.parse(a.startsAt) - Date.parse(b.startsAt) || Date.parse(a.endsAt) - Date.parse(b.endsAt))
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
events: events.slice(0, limit),
|
|
63
|
+
truncated: events.length > limit,
|
|
64
|
+
}
|
|
65
|
+
}
|