@glassly/engine 0.1.0-dev.1 → 0.1.0-dev.100
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 +57 -19
- package/build/bluetooth-sdk/debug.d.ts +2 -0
- package/build/bluetooth-sdk/debug.d.ts.map +1 -0
- package/build/bluetooth-sdk/debug.js +2 -0
- package/build/bluetooth-sdk/debug.js.map +1 -0
- package/build/bluetooth-sdk/index.d.ts +3 -0
- package/build/bluetooth-sdk/index.d.ts.map +1 -0
- package/build/bluetooth-sdk/index.js +3 -0
- package/build/bluetooth-sdk/index.js.map +1 -0
- package/build/bluetooth-sdk/ota-transport.d.ts +2 -0
- package/build/bluetooth-sdk/ota-transport.d.ts.map +1 -0
- package/build/bluetooth-sdk/ota-transport.js +2 -0
- package/build/bluetooth-sdk/ota-transport.js.map +1 -0
- package/build/bluetooth-sdk/photo-receiver.d.ts +3 -0
- package/build/bluetooth-sdk/photo-receiver.d.ts.map +1 -0
- package/build/bluetooth-sdk/photo-receiver.js +3 -0
- package/build/bluetooth-sdk/photo-receiver.js.map +1 -0
- package/build/bluetooth-sdk/react.d.ts +2 -0
- package/build/bluetooth-sdk/react.d.ts.map +1 -0
- package/build/bluetooth-sdk/react.js +2 -0
- package/build/bluetooth-sdk/react.js.map +1 -0
- package/build/bluetooth-sdk/types.d.ts +2 -0
- package/build/bluetooth-sdk/types.d.ts.map +1 -0
- package/build/bluetooth-sdk/types.js +2 -0
- package/build/bluetooth-sdk/types.js.map +1 -0
- package/build/engine.d.ts +30 -18
- package/build/engine.d.ts.map +1 -1
- package/build/engine.js +12 -3
- package/build/engine.js.map +1 -1
- package/build/facades/dev.d.ts +1 -1
- package/build/facades/display.d.ts +11 -4
- package/build/facades/display.d.ts.map +1 -1
- package/build/facades/display.js +12 -2
- package/build/facades/display.js.map +1 -1
- package/build/facades/displayMirror.d.ts +2 -6
- package/build/facades/displayMirror.d.ts.map +1 -1
- package/build/facades/displayMirror.js +7 -14
- package/build/facades/displayMirror.js.map +1 -1
- package/build/facades/glasses.d.ts +6 -13
- package/build/facades/glasses.d.ts.map +1 -1
- package/build/facades/glasses.js +7 -3
- package/build/facades/glasses.js.map +1 -1
- package/build/facades/ota.d.ts +21 -21
- package/build/facades/ota.d.ts.map +1 -1
- package/build/facades/ota.js +26 -10
- package/build/facades/ota.js.map +1 -1
- package/build/facades/pairing.d.ts +11 -5
- package/build/facades/pairing.d.ts.map +1 -1
- package/build/facades/pairing.js +93 -17
- package/build/facades/pairing.js.map +1 -1
- package/build/facades/phoneNotifications.d.ts +12 -2
- package/build/facades/phoneNotifications.d.ts.map +1 -1
- package/build/facades/phoneNotifications.js +30 -10
- package/build/facades/phoneNotifications.js.map +1 -1
- package/build/facades/speakers.d.ts +1 -1
- package/build/facades/speakers.d.ts.map +1 -1
- package/build/facades/speakers.js.map +1 -1
- package/build/facades/speech.d.ts +7 -6
- package/build/facades/speech.d.ts.map +1 -1
- package/build/facades/speech.js +1 -1
- package/build/facades/speech.js.map +1 -1
- package/build/generated/releaseMetadata.d.ts +12 -0
- package/build/generated/releaseMetadata.d.ts.map +1 -0
- package/build/generated/releaseMetadata.js +10 -0
- package/build/generated/releaseMetadata.js.map +1 -0
- package/build/index.d.ts +9 -6
- package/build/index.d.ts.map +1 -1
- package/build/index.js +7 -5
- package/build/index.js.map +1 -1
- package/build/react/MentraLiveOtaFlow.d.ts +44 -0
- package/build/react/MentraLiveOtaFlow.d.ts.map +1 -0
- package/build/react/MentraLiveOtaFlow.js +322 -0
- package/build/react/MentraLiveOtaFlow.js.map +1 -0
- package/build/react/index.d.ts +3 -0
- package/build/react/index.d.ts.map +1 -0
- package/build/react/index.js +3 -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/react/useMentraLiveOta.d.ts +84 -0
- package/build/react/useMentraLiveOta.d.ts.map +1 -0
- package/build/react/useMentraLiveOta.js +627 -0
- package/build/react/useMentraLiveOta.js.map +1 -0
- package/build/services/AppRegistry.d.ts +8 -0
- package/build/services/AppRegistry.d.ts.map +1 -1
- package/build/services/AppRegistry.js +15 -0
- package/build/services/AppRegistry.js.map +1 -1
- package/build/services/BlobStore.d.ts +2 -0
- package/build/services/BlobStore.d.ts.map +1 -1
- package/build/services/BlobStore.js +63 -10
- package/build/services/BlobStore.js.map +1 -1
- package/build/services/CustomDashboardModel.d.ts +48 -0
- package/build/services/CustomDashboardModel.d.ts.map +1 -0
- package/build/services/CustomDashboardModel.js +73 -0
- package/build/services/CustomDashboardModel.js.map +1 -0
- package/build/services/CustomDashboardService.d.ts +55 -0
- package/build/services/CustomDashboardService.d.ts.map +1 -0
- package/build/services/CustomDashboardService.js +492 -0
- package/build/services/CustomDashboardService.js.map +1 -0
- package/build/services/DashboardCoordinator.d.ts +26 -0
- package/build/services/DashboardCoordinator.d.ts.map +1 -0
- package/build/services/DashboardCoordinator.js +125 -0
- package/build/services/DashboardCoordinator.js.map +1 -0
- package/build/services/DeviceEventRouter.d.ts.map +1 -1
- package/build/services/DeviceEventRouter.js +45 -6
- package/build/services/DeviceEventRouter.js.map +1 -1
- package/build/services/DisplayProcessor.d.ts +3 -3
- package/build/services/DisplayProcessor.js +3 -3
- package/build/services/DisplayProcessor.js.map +1 -1
- package/build/services/GestureDotsService.d.ts +40 -0
- package/build/services/GestureDotsService.d.ts.map +1 -0
- package/build/services/GestureDotsService.js +167 -0
- package/build/services/GestureDotsService.js.map +1 -0
- package/build/services/GlassesStatusProjection.d.ts +15 -1
- package/build/services/GlassesStatusProjection.d.ts.map +1 -1
- package/build/services/GlassesStatusProjection.js +18 -11
- package/build/services/GlassesStatusProjection.js.map +1 -1
- package/build/services/HotspotOtaTransport.d.ts.map +1 -1
- package/build/services/HotspotOtaTransport.js +8 -7
- package/build/services/HotspotOtaTransport.js.map +1 -1
- package/build/services/LocalDisplayManager.d.ts +55 -3
- package/build/services/LocalDisplayManager.d.ts.map +1 -1
- package/build/services/LocalDisplayManager.js +196 -16
- package/build/services/LocalDisplayManager.js.map +1 -1
- package/build/services/LocalMiniappRuntime.d.ts +13 -0
- package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
- package/build/services/LocalMiniappRuntime.js +32 -0
- package/build/services/LocalMiniappRuntime.js.map +1 -1
- package/build/services/MiniappExitGesture.d.ts +33 -0
- package/build/services/MiniappExitGesture.d.ts.map +1 -0
- package/build/services/MiniappExitGesture.js +54 -0
- package/build/services/MiniappExitGesture.js.map +1 -0
- package/build/services/OtaAutoChain.d.ts +41 -0
- package/build/services/OtaAutoChain.d.ts.map +1 -0
- package/build/services/OtaAutoChain.js +91 -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 +26 -3
- package/build/services/OtaInstallCoordinator.d.ts.map +1 -1
- package/build/services/OtaInstallCoordinator.js +38 -13
- package/build/services/OtaInstallCoordinator.js.map +1 -1
- package/build/services/OtaService.d.ts.map +1 -1
- package/build/services/OtaService.js +1 -1
- package/build/services/OtaService.js.map +1 -1
- package/build/services/OtaUpdateCheckService.d.ts +4 -1
- package/build/services/OtaUpdateCheckService.d.ts.map +1 -1
- package/build/services/OtaUpdateCheckService.js +28 -16
- package/build/services/OtaUpdateCheckService.js.map +1 -1
- package/build/services/PhoneNotificationsSync.d.ts +13 -0
- package/build/services/PhoneNotificationsSync.d.ts.map +1 -1
- package/build/services/PhoneNotificationsSync.js +101 -9
- package/build/services/PhoneNotificationsSync.js.map +1 -1
- package/build/services/PhonePhotoCoordinator.js +1 -1
- package/build/services/PhonePhotoCoordinator.js.map +1 -1
- package/build/services/PhoneStreamCoordinator.d.ts.map +1 -1
- package/build/services/PhoneStreamCoordinator.js +9 -6
- package/build/services/PhoneStreamCoordinator.js.map +1 -1
- package/build/services/SceneRenderer.d.ts.map +1 -1
- package/build/services/SceneRenderer.js +5 -0
- package/build/services/SceneRenderer.js.map +1 -1
- package/build/services/StreamLifecycleController.d.ts +4 -0
- package/build/services/StreamLifecycleController.d.ts.map +1 -1
- package/build/services/StreamLifecycleController.js +4 -0
- package/build/services/StreamLifecycleController.js.map +1 -1
- package/build/services/asg/localNetworkTransport.d.ts.map +1 -1
- package/build/services/asg/localNetworkTransport.js +13 -12
- package/build/services/asg/localNetworkTransport.js.map +1 -1
- package/build/services/effectiveCapabilities.d.ts +1 -1
- package/build/services/effectiveCapabilities.d.ts.map +1 -1
- package/build/services/effectiveCapabilities.js +26 -7
- package/build/services/effectiveCapabilities.js.map +1 -1
- package/build/services/gestureClaims.d.ts +18 -0
- package/build/services/gestureClaims.d.ts.map +1 -0
- package/build/services/gestureClaims.js +24 -0
- package/build/services/gestureClaims.js.map +1 -0
- package/build/services/gestureDotGlyphs.d.ts +8 -0
- package/build/services/gestureDotGlyphs.d.ts.map +1 -0
- package/build/services/gestureDotGlyphs.js +13 -0
- package/build/services/gestureDotGlyphs.js.map +1 -0
- package/build/services/glassesClockSync.d.ts.map +1 -1
- package/build/services/glassesClockSync.js +5 -1
- package/build/services/glassesClockSync.js.map +1 -1
- package/build/services/iosNotificationAppCatalog.d.ts +13 -0
- package/build/services/iosNotificationAppCatalog.d.ts.map +1 -0
- package/build/services/iosNotificationAppCatalog.js +28 -0
- package/build/services/iosNotificationAppCatalog.js.map +1 -0
- package/build/services/otaDisplayState.d.ts +1 -1
- package/build/services/otaDisplayState.d.ts.map +1 -1
- package/build/services/otaDisplayState.js.map +1 -1
- package/build/services/otaInstallPolicy.d.ts +5 -1
- package/build/services/otaInstallPolicy.d.ts.map +1 -1
- package/build/services/otaInstallPolicy.js +4 -0
- package/build/services/otaInstallPolicy.js.map +1 -1
- package/build/services/otaLegacyMapping.d.ts +1 -1
- package/build/services/otaLegacyMapping.d.ts.map +1 -1
- package/build/services/otaLegacyMapping.js.map +1 -1
- package/build/services/otaManifestPolicy.d.ts +14 -0
- package/build/services/otaManifestPolicy.d.ts.map +1 -0
- package/build/services/otaManifestPolicy.js +20 -0
- package/build/services/otaManifestPolicy.js.map +1 -0
- package/build/services/otaManifestUrl.d.ts +2 -1
- package/build/services/otaManifestUrl.d.ts.map +1 -1
- package/build/services/otaManifestUrl.js +24 -32
- package/build/services/otaManifestUrl.js.map +1 -1
- package/build/services/otaReleaseVersion.d.ts +10 -0
- package/build/services/otaReleaseVersion.d.ts.map +1 -0
- package/build/services/otaReleaseVersion.js +14 -0
- package/build/services/otaReleaseVersion.js.map +1 -0
- package/build/stores/appVisibility.d.ts +3 -0
- package/build/stores/appVisibility.d.ts.map +1 -0
- package/build/stores/appVisibility.js +3 -0
- package/build/stores/appVisibility.js.map +1 -0
- package/build/stores/apps.d.ts.map +1 -1
- package/build/stores/apps.js +7 -1
- package/build/stores/apps.js.map +1 -1
- package/build/stores/core.d.ts +3 -3
- package/build/stores/core.d.ts.map +1 -1
- package/build/stores/core.js.map +1 -1
- package/build/stores/display.d.ts +4 -4
- package/build/stores/display.d.ts.map +1 -1
- package/build/stores/display.js +12 -36
- package/build/stores/display.js.map +1 -1
- package/build/stores/glasses.d.ts +10 -4
- package/build/stores/glasses.d.ts.map +1 -1
- package/build/stores/glasses.js +2 -0
- package/build/stores/glasses.js.map +1 -1
- package/build/stores/settings.d.ts.map +1 -1
- package/build/stores/settings.js +140 -5
- package/build/stores/settings.js.map +1 -1
- package/build/types/applet.d.ts +3 -1
- package/build/types/applet.d.ts.map +1 -1
- package/build/types/applet.js +0 -3
- package/build/types/applet.js.map +1 -1
- package/build/types/capabilities/even-realities-g2.d.ts.map +1 -1
- package/build/types/capabilities/even-realities-g2.js +2 -2
- package/build/types/capabilities/even-realities-g2.js.map +1 -1
- package/build/types/enums.d.ts +1 -5
- package/build/types/enums.d.ts.map +1 -1
- package/build/types/enums.js +1 -8
- package/build/types/enums.js.map +1 -1
- package/build/types/hardware.d.ts +1 -1
- package/build/types/hardware.d.ts.map +1 -1
- package/build/types/hardware.js +1 -0
- package/build/types/hardware.js.map +1 -1
- package/build/types/index.d.ts +3 -7
- package/build/types/index.d.ts.map +1 -1
- package/build/types/index.js +3 -7
- package/build/types/index.js.map +1 -1
- package/build/utils/display/composer/ColumnComposer.d.ts +1 -1
- package/build/utils/display/composer/ColumnComposer.js +1 -1
- package/build/utils/display/composer/ColumnComposer.js.map +1 -1
- package/build/utils/display/scene/process.d.ts.map +1 -1
- package/build/utils/display/scene/process.js +4 -3
- package/build/utils/display/scene/process.js.map +1 -1
- package/build/utils/display/scene/types.d.ts +6 -2
- package/build/utils/display/scene/types.d.ts.map +1 -1
- package/build/utils/display/scene/types.js.map +1 -1
- package/package.json +69 -35
- package/src/bluetooth-sdk/debug.ts +1 -0
- package/src/bluetooth-sdk/index.ts +2 -0
- package/src/bluetooth-sdk/ota-transport.ts +1 -0
- package/src/bluetooth-sdk/photo-receiver.ts +2 -0
- package/src/bluetooth-sdk/react.ts +1 -0
- package/src/bluetooth-sdk/types.ts +1 -0
- package/src/engine.ts +12 -3
- package/src/facades/display.ts +12 -2
- package/src/facades/displayMirror.ts +7 -15
- package/src/facades/glasses.ts +8 -4
- package/src/facades/ota.ts +36 -11
- package/src/facades/pairing.ts +103 -23
- package/src/facades/phoneNotifications.ts +32 -10
- package/src/facades/speakers.ts +1 -1
- package/src/facades/speech.ts +21 -7
- package/src/generated/releaseMetadata.ts +20 -0
- package/src/index.ts +31 -5
- package/src/react/MentraLiveOtaFlow.tsx +754 -0
- package/src/react/index.ts +21 -0
- package/src/react/useEngineSnapshot.ts +16 -0
- package/src/react/useMentraLiveOta.ts +793 -0
- package/src/services/AppRegistry.ts +16 -0
- package/src/services/BlobStore.ts +57 -10
- package/src/services/CustomDashboardModel.ts +83 -0
- package/src/services/CustomDashboardService.ts +516 -0
- package/src/services/DashboardCoordinator.ts +121 -0
- package/src/services/DeviceEventRouter.ts +48 -8
- package/src/services/DisplayProcessor.ts +3 -3
- package/src/services/GestureDotsService.ts +164 -0
- package/src/services/GlassesStatusProjection.ts +20 -11
- package/src/services/HotspotOtaTransport.ts +8 -7
- package/src/services/LocalDisplayManager.ts +198 -18
- package/src/services/LocalMiniappRuntime.ts +37 -0
- package/src/services/MiniappExitGesture.ts +76 -0
- package/src/services/OtaAutoChain.ts +130 -0
- package/src/services/OtaErrorMapping.ts +80 -0
- package/src/services/OtaInstallCoordinator.ts +40 -13
- package/src/services/OtaService.ts +1 -2
- package/src/services/OtaUpdateCheckService.ts +33 -19
- package/src/services/PhoneNotificationsSync.ts +125 -9
- package/src/services/PhonePhotoCoordinator.ts +1 -1
- package/src/services/PhoneStreamCoordinator.ts +10 -6
- package/src/services/SceneRenderer.ts +4 -0
- package/src/services/StreamLifecycleController.ts +4 -0
- package/src/services/asg/localNetworkTransport.ts +20 -19
- package/src/services/effectiveCapabilities.ts +27 -7
- package/src/services/gestureClaims.ts +28 -0
- package/src/services/gestureDotGlyphs.ts +15 -0
- package/src/services/glassesClockSync.ts +5 -1
- package/src/services/iosNotificationAppCatalog.ts +39 -0
- package/src/services/otaDisplayState.ts +1 -1
- package/src/services/otaInstallPolicy.ts +5 -1
- package/src/services/otaLegacyMapping.ts +1 -1
- package/src/services/otaManifestPolicy.ts +43 -0
- package/src/services/otaManifestUrl.ts +27 -36
- package/src/services/otaReleaseVersion.ts +22 -0
- package/src/stores/appVisibility.ts +3 -0
- package/src/stores/apps.ts +7 -1
- package/src/stores/core.ts +4 -4
- package/src/stores/display.ts +16 -48
- package/src/stores/glasses.ts +15 -6
- package/src/stores/settings.ts +140 -5
- package/src/types/applet.ts +3 -1
- package/src/types/capabilities/even-realities-g2.ts +3 -2
- package/src/types/enums.ts +1 -9
- package/src/types/hardware.ts +2 -1
- package/src/types/index.ts +3 -7
- package/src/utils/display/composer/ColumnComposer.ts +1 -1
- package/src/utils/display/scene/process.ts +4 -3
- package/src/utils/display/scene/types.ts +0 -0
- package/build/devtools.d.ts +0 -11
- package/build/devtools.d.ts.map +0 -1
- package/build/devtools.js +0 -13
- package/build/devtools.js.map +0 -1
- package/build/internal.d.ts +0 -67
- package/build/internal.d.ts.map +0 -1
- package/build/internal.js +0 -90
- package/build/internal.js.map +0 -1
- package/src/devtools.ts +0 -13
- package/src/internal.ts +0 -155
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* LocalDisplayManager
|
|
3
3
|
*
|
|
4
4
|
* Phone-side arbiter for display requests originating from LOCAL miniapps
|
|
5
|
-
* (the @glassly/miniapp SDK).
|
|
6
|
-
*
|
|
5
|
+
* (the @glassly/miniapp SDK). Preserves the retired cloud display manager's
|
|
6
|
+
* user-facing behavior for the features that matter on the phone:
|
|
7
7
|
*
|
|
8
8
|
* - boot message ("Starting <AppName>…") with a bounded window
|
|
9
9
|
* - durationMs auto-clear
|
|
@@ -115,12 +115,50 @@ class LocalDisplayManager {
|
|
|
115
115
|
|
|
116
116
|
private coreApp: string | null = null
|
|
117
117
|
private coreAppDisplay: ActiveDisplay | null = null
|
|
118
|
-
private
|
|
118
|
+
private _currentDisplay: ActiveDisplay | null = null
|
|
119
119
|
private backgroundLock: BackgroundLock | null = null
|
|
120
120
|
|
|
121
|
+
/** Fired when the display transitions between empty and occupied (any package, system screens included). */
|
|
122
|
+
private activeDisplayListeners = new Set<(active: boolean) => void>()
|
|
123
|
+
|
|
124
|
+
private get currentDisplay(): ActiveDisplay | null {
|
|
125
|
+
return this._currentDisplay
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private set currentDisplay(value: ActiveDisplay | null) {
|
|
129
|
+
const wasActive = this._currentDisplay !== null
|
|
130
|
+
this._currentDisplay = value
|
|
131
|
+
const isActive = value !== null
|
|
132
|
+
if (wasActive === isActive) return
|
|
133
|
+
for (const listener of this.activeDisplayListeners) {
|
|
134
|
+
try {
|
|
135
|
+
listener(isActive)
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.error(`${LOG_TAG}: onActiveDisplayChange listener failed:`, error)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** True while anything (miniapp or system screen) is on the glasses display. */
|
|
143
|
+
public hasActiveDisplay(): boolean {
|
|
144
|
+
return this._currentDisplay !== null
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Subscribe to empty↔occupied display transitions. Returns an unsubscribe. */
|
|
148
|
+
public onActiveDisplayChange(listener: (active: boolean) => void): () => void {
|
|
149
|
+
this.activeDisplayListeners.add(listener)
|
|
150
|
+
return () => this.activeDisplayListeners.delete(listener)
|
|
151
|
+
}
|
|
152
|
+
|
|
121
153
|
private bootingApp: BootingApp | null = null
|
|
122
154
|
private bootQueue: Map<string, {payload: DisplayPayload; resolve?: DisplayRequestResolver}> = new Map()
|
|
123
155
|
|
|
156
|
+
/** While true the dashboard owns the glasses: main frames are retained but not sent. */
|
|
157
|
+
private dashboardActive = false
|
|
158
|
+
|
|
159
|
+
/** Miniapp whose visible frame is temporarily covered by an OS exit notice. */
|
|
160
|
+
private exitingApp: {packageName: string; coveredDisplay: ActiveDisplay} | null = null
|
|
161
|
+
|
|
124
162
|
private expiryTimerId: number | null = null
|
|
125
163
|
|
|
126
164
|
/** Reconnect-replay subscription state (attachToRuntime). */
|
|
@@ -200,7 +238,7 @@ class LocalDisplayManager {
|
|
|
200
238
|
*/
|
|
201
239
|
public showSystemNotice(text: string, durationMs: number = SYSTEM_NOTICE_DURATION_MS): void {
|
|
202
240
|
if (!isGlassesConnected(useGlassesStore.getState().connection)) return
|
|
203
|
-
if (this.bootingApp) return
|
|
241
|
+
if (this.bootingApp || this.exitingApp) return
|
|
204
242
|
console.log(`${LOG_TAG}: showSystemNotice("${text}")`)
|
|
205
243
|
this.sendNow(SYSTEM_NOTICE_PKG, {
|
|
206
244
|
view: "main",
|
|
@@ -228,6 +266,88 @@ class LocalDisplayManager {
|
|
|
228
266
|
}
|
|
229
267
|
}
|
|
230
268
|
|
|
269
|
+
/**
|
|
270
|
+
* The package that currently owns the glasses screen, for gesture-claim
|
|
271
|
+
* arbitration (see LocalMiniappRuntime.foregroundClaimsGesture). This is
|
|
272
|
+
* glasses ownership, not the phone's Compositor foreground: a background
|
|
273
|
+
* miniapp rendering to the glasses with the phone pocketed is the owner.
|
|
274
|
+
*
|
|
275
|
+
* The live frame's owner wins; a background app that holds the display lock
|
|
276
|
+
* but has no frame up right now (between renders) still counts. System
|
|
277
|
+
* frames (boot, notices) own nothing claimable. Returns null when the
|
|
278
|
+
* glasses are idle.
|
|
279
|
+
*/
|
|
280
|
+
public getGestureOwner(): string | null {
|
|
281
|
+
const current = this.currentDisplay?.packageName ?? null
|
|
282
|
+
if (current && current !== SYSTEM_BOOT_PKG && current !== SYSTEM_NOTICE_PKG) return current
|
|
283
|
+
const lock = this.backgroundLock
|
|
284
|
+
if (lock && this.now() <= lock.expiresAt) return lock.packageName
|
|
285
|
+
return null
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* The miniapp whose frame is visibly on the glasses right now.
|
|
290
|
+
*
|
|
291
|
+
* Unlike getGestureOwner(), this deliberately does not fall back to a
|
|
292
|
+
* background display lock: a lock holder with no live frame is not visible
|
|
293
|
+
* and therefore must not be closed by the system double-tap gesture.
|
|
294
|
+
*/
|
|
295
|
+
public getVisibleMiniappPackageName(): string | null {
|
|
296
|
+
const current = this.currentDisplay?.packageName ?? null
|
|
297
|
+
if (!current || current === SYSTEM_BOOT_PKG || current === SYSTEM_NOTICE_PKG) return null
|
|
298
|
+
return current
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Cover the currently visible miniapp with a persistent system exit notice.
|
|
303
|
+
* While it is up, miniapp display traffic is held off so a streaming app
|
|
304
|
+
* cannot immediately overwrite the notice. The caller completes the normal
|
|
305
|
+
* stop flow and then calls finishMiniappExit(); onUnmount does the same as a
|
|
306
|
+
* safety net.
|
|
307
|
+
*/
|
|
308
|
+
public beginMiniappExit(packageName: string, text: string): boolean {
|
|
309
|
+
if (this.exitingApp !== null) return this.exitingApp.packageName === packageName
|
|
310
|
+
const coveredDisplay = this.currentDisplay
|
|
311
|
+
if (!coveredDisplay || this.getVisibleMiniappPackageName() !== packageName) return false
|
|
312
|
+
|
|
313
|
+
console.log(`${LOG_TAG}: beginMiniappExit(${packageName})`)
|
|
314
|
+
this.exitingApp = {packageName, coveredDisplay}
|
|
315
|
+
this.clearExpiryTimer()
|
|
316
|
+
this.sendNow(SYSTEM_NOTICE_PKG, {
|
|
317
|
+
view: "main",
|
|
318
|
+
layout: {layoutType: "text_wall", text},
|
|
319
|
+
})
|
|
320
|
+
return true
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** Remove an exit notice and resume the display state normal app teardown leaves behind. */
|
|
324
|
+
public finishMiniappExit(packageName: string): void {
|
|
325
|
+
if (this.exitingApp?.packageName !== packageName) return
|
|
326
|
+
console.log(`${LOG_TAG}: finishMiniappExit(${packageName})`)
|
|
327
|
+
this.exitingApp = null
|
|
328
|
+
|
|
329
|
+
if (this.currentDisplay?.packageName !== SYSTEM_NOTICE_PKG) return
|
|
330
|
+
this.currentDisplay = null
|
|
331
|
+
this.tryRestoreCoreDisplay()
|
|
332
|
+
if (!this.currentDisplay) this.sendClear()
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** Cancel the handoff and put back the frame the exit notice covered. */
|
|
336
|
+
public cancelMiniappExit(packageName: string): void {
|
|
337
|
+
if (this.exitingApp?.packageName !== packageName) return
|
|
338
|
+
const covered = this.exitingApp.coveredDisplay
|
|
339
|
+
this.exitingApp = null
|
|
340
|
+
|
|
341
|
+
if (this.currentDisplay?.packageName !== SYSTEM_NOTICE_PKG) return
|
|
342
|
+
if (covered.expiresAt === null || covered.expiresAt > this.now()) {
|
|
343
|
+
this.restoreDisplay(covered)
|
|
344
|
+
} else {
|
|
345
|
+
this.currentDisplay = null
|
|
346
|
+
this.tryRestoreCoreDisplay()
|
|
347
|
+
if (!this.currentDisplay) this.sendClear()
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
231
351
|
/** Report-safe ownership snapshot for incident diagnostics. */
|
|
232
352
|
public getDiagnosticSnapshot(): Record<string, unknown> {
|
|
233
353
|
return {
|
|
@@ -235,6 +355,7 @@ class LocalDisplayManager {
|
|
|
235
355
|
currentDisplayPackageName: this.currentDisplay?.packageName ?? null,
|
|
236
356
|
backgroundLockPackageName: this.backgroundLock?.packageName ?? null,
|
|
237
357
|
bootingPackageName: this.bootingApp?.packageName ?? null,
|
|
358
|
+
exitingPackageName: this.exitingApp?.packageName ?? null,
|
|
238
359
|
}
|
|
239
360
|
}
|
|
240
361
|
|
|
@@ -297,6 +418,49 @@ class LocalDisplayManager {
|
|
|
297
418
|
this.sendClear()
|
|
298
419
|
}
|
|
299
420
|
}
|
|
421
|
+
|
|
422
|
+
this.finishMiniappExit(packageName)
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// ===========================================================================
|
|
426
|
+
// Dashboard switching (called from DashboardCoordinator)
|
|
427
|
+
// ===========================================================================
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Dashboard cover state. On deactivate the retained main frame repaints
|
|
431
|
+
* (scenes replay create-based; legacy re-sends), or the glasses clear.
|
|
432
|
+
*/
|
|
433
|
+
public setDashboardActive(active: boolean): void {
|
|
434
|
+
if (this.dashboardActive === active) return
|
|
435
|
+
this.dashboardActive = active
|
|
436
|
+
useDisplayStore.getState().setDashboardActive(active)
|
|
437
|
+
if (active) return
|
|
438
|
+
sceneRenderer.markViewStale("dashboard")
|
|
439
|
+
const current = this.currentDisplay
|
|
440
|
+
if (current && (current.expiresAt === null || this.now() < current.expiresAt)) {
|
|
441
|
+
this.replayCurrent()
|
|
442
|
+
} else {
|
|
443
|
+
this.sendClear()
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/** Dashboard renderers only; bypasses arbitration and main ownership state. */
|
|
448
|
+
public renderDashboard(packageName: string, payload: DisplayPayload): void {
|
|
449
|
+
if (!this.dashboardActive) return
|
|
450
|
+
if (payload.scene) {
|
|
451
|
+
sceneRenderer.emitScene(packageName, "dashboard", payload.scene)
|
|
452
|
+
return
|
|
453
|
+
}
|
|
454
|
+
if (!payload.layout) return
|
|
455
|
+
const rawEvent = {view: "dashboard", layout: payload.layout}
|
|
456
|
+
let processedEvent: Record<string, unknown>
|
|
457
|
+
try {
|
|
458
|
+
processedEvent = displayProcessor.processDisplayEvent(rawEvent as any)
|
|
459
|
+
} catch (err) {
|
|
460
|
+
console.error(`${LOG_TAG}: DisplayProcessor error, using raw event:`, err)
|
|
461
|
+
processedEvent = rawEvent
|
|
462
|
+
}
|
|
463
|
+
this.pushToNative(processedEvent)
|
|
300
464
|
}
|
|
301
465
|
|
|
302
466
|
// ===========================================================================
|
|
@@ -313,10 +477,18 @@ class LocalDisplayManager {
|
|
|
313
477
|
* the boot queue (resolved when drained or superseded).
|
|
314
478
|
*/
|
|
315
479
|
public request(packageName: string, payload: DisplayPayload, resolve?: DisplayRequestResolver): void {
|
|
316
|
-
//
|
|
317
|
-
//
|
|
480
|
+
// Keep the OS exit notice visible for its short handoff window. Without
|
|
481
|
+
// this, streaming captions/terminal frames can replace it immediately.
|
|
482
|
+
if (this.exitingApp !== null) {
|
|
483
|
+
resolve?.({status: "blocked", reason: "a miniapp exit notice is active"})
|
|
484
|
+
return
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Dashboard view: only visible while the dashboard covers the glasses.
|
|
488
|
+
// Local miniapp dashboard content remains a stub.
|
|
318
489
|
if (payload.view === "dashboard") {
|
|
319
|
-
this.
|
|
490
|
+
this.renderDashboard(packageName, payload)
|
|
491
|
+
resolve?.({status: "displayed"})
|
|
320
492
|
return
|
|
321
493
|
}
|
|
322
494
|
|
|
@@ -557,6 +729,23 @@ class LocalDisplayManager {
|
|
|
557
729
|
processedEvent = rawEvent
|
|
558
730
|
}
|
|
559
731
|
|
|
732
|
+
// While the dashboard covers the glasses, main frames only update the
|
|
733
|
+
// retained state; setDashboardActive(false) replays the latest.
|
|
734
|
+
if (!this.dashboardActive) this.pushToNative(processedEvent)
|
|
735
|
+
|
|
736
|
+
this.currentDisplay = {packageName, processedEvent, expiresAt}
|
|
737
|
+
|
|
738
|
+
this.clearExpiryTimer()
|
|
739
|
+
if (expiresAt !== null) {
|
|
740
|
+
const delay = Math.max(0, expiresAt - this.now())
|
|
741
|
+
this.expiryTimerId = BgTimer.setTimeout(() => {
|
|
742
|
+
this.expiryTimerId = null
|
|
743
|
+
this.handleExpiry(packageName)
|
|
744
|
+
}, delay)
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
private pushToNative(processedEvent: Record<string, unknown>): void {
|
|
560
749
|
try {
|
|
561
750
|
// The display output path is a direct btsdk call now (was a host sendDisplayEvent
|
|
562
751
|
// hook) so a bare OEM renders to the glasses without wiring it.
|
|
@@ -569,17 +758,6 @@ class LocalDisplayManager {
|
|
|
569
758
|
} catch (err) {
|
|
570
759
|
console.error(`${LOG_TAG}: native display failed:`, err)
|
|
571
760
|
}
|
|
572
|
-
|
|
573
|
-
this.currentDisplay = {packageName, processedEvent, expiresAt}
|
|
574
|
-
|
|
575
|
-
this.clearExpiryTimer()
|
|
576
|
-
if (expiresAt !== null) {
|
|
577
|
-
const delay = Math.max(0, expiresAt - this.now())
|
|
578
|
-
this.expiryTimerId = BgTimer.setTimeout(() => {
|
|
579
|
-
this.expiryTimerId = null
|
|
580
|
-
this.handleExpiry(packageName)
|
|
581
|
-
}, delay)
|
|
582
|
-
}
|
|
583
761
|
}
|
|
584
762
|
|
|
585
763
|
private handleExpiry(packageName: string): void {
|
|
@@ -785,6 +963,8 @@ class LocalDisplayManager {
|
|
|
785
963
|
this.coreAppDisplay = null
|
|
786
964
|
this.currentDisplay = null
|
|
787
965
|
this.backgroundLock = null
|
|
966
|
+
this.exitingApp = null
|
|
967
|
+
this.dashboardActive = false
|
|
788
968
|
this.bootQueue.clear()
|
|
789
969
|
this.now = () => Date.now()
|
|
790
970
|
this.unsubscribeGlassesStatus?.()
|
|
@@ -52,6 +52,7 @@ import {normalizeAiTier, resolveAiTier} from "./AiTierResolution"
|
|
|
52
52
|
import type {AiTier, AiTierProvider, SavedLlmKeys} from "./AiTierResolution"
|
|
53
53
|
import {HttpError} from "@glassly/cloud-client"
|
|
54
54
|
import localDisplayManager from "./LocalDisplayManager"
|
|
55
|
+
import {claimsGesture} from "./gestureClaims"
|
|
55
56
|
import nativeNavigationService from "./NativeNavigationService"
|
|
56
57
|
import type {DisplayPayload} from "./LocalDisplayManager"
|
|
57
58
|
import headingService from "./HeadingService"
|
|
@@ -539,6 +540,8 @@ class LocalMiniappRuntime {
|
|
|
539
540
|
|
|
540
541
|
/** Connected miniapps keyed by packageName. */
|
|
541
542
|
private connectedApps: Map<string, ConnectedMiniapp> = new Map()
|
|
543
|
+
/** Per-app claimed gestures (CLAIM_GESTURES) — see foregroundClaimsGesture. */
|
|
544
|
+
private gestureClaims: Map<string, Set<string>> = new Map()
|
|
542
545
|
|
|
543
546
|
/**
|
|
544
547
|
* Packages that have completed their CONNECT handshake (sent
|
|
@@ -1173,6 +1176,7 @@ class LocalMiniappRuntime {
|
|
|
1173
1176
|
// core app's saved frame).
|
|
1174
1177
|
localDisplayManager.onUnmount(packageName)
|
|
1175
1178
|
|
|
1179
|
+
this.gestureClaims.delete(packageName)
|
|
1176
1180
|
this.connectedApps.delete(packageName)
|
|
1177
1181
|
this.recomputeMicRequirements()
|
|
1178
1182
|
if (releasedMicGateOverride) {
|
|
@@ -1251,6 +1255,9 @@ class LocalMiniappRuntime {
|
|
|
1251
1255
|
case MiniappRequestType.SUBSCRIBE:
|
|
1252
1256
|
this.handleSubscribe(packageName, payload, requestId)
|
|
1253
1257
|
break
|
|
1258
|
+
case MiniappRequestType.CLAIM_GESTURES:
|
|
1259
|
+
this.handleClaimGestures(packageName, payload, requestId)
|
|
1260
|
+
break
|
|
1254
1261
|
case MiniappRequestType.DISPLAY:
|
|
1255
1262
|
this.handleDisplay(packageName, payload, requestId)
|
|
1256
1263
|
break
|
|
@@ -1858,6 +1865,36 @@ class LocalMiniappRuntime {
|
|
|
1858
1865
|
this.sendResult(packageName, requestId, true, {applied: true})
|
|
1859
1866
|
}
|
|
1860
1867
|
|
|
1868
|
+
/**
|
|
1869
|
+
* CLAIM_GESTURES (session.input.setClaimedGestures): the app declares which
|
|
1870
|
+
* touch gestures it handles itself, replacing its previous claim set.
|
|
1871
|
+
* Claims suppress the host's default behavior for those gestures (today:
|
|
1872
|
+
* double_tap → dashboard/head-up in DeviceEventRouter) but ONLY while the
|
|
1873
|
+
* claiming app is the one in front of the user — see
|
|
1874
|
+
* {@link foregroundClaimsGesture}. Cleared on app stop.
|
|
1875
|
+
*/
|
|
1876
|
+
private handleClaimGestures(packageName: string, payload: Record<string, unknown>, requestId?: string): void {
|
|
1877
|
+
if (!this.connectedApps.has(packageName)) {
|
|
1878
|
+
this.sendResult(packageName, requestId, true)
|
|
1879
|
+
return
|
|
1880
|
+
}
|
|
1881
|
+
const raw = payload.gestures
|
|
1882
|
+
const gestures = Array.isArray(raw) ? raw.filter((g): g is string => typeof g === "string") : []
|
|
1883
|
+
if (gestures.length === 0) this.gestureClaims.delete(packageName)
|
|
1884
|
+
else this.gestureClaims.set(packageName, new Set(gestures))
|
|
1885
|
+
this.sendResult(packageName, requestId, true)
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
/** True iff the miniapp in front of the user has claimed `gesture`. */
|
|
1889
|
+
public foregroundClaimsGesture(gesture: string): boolean {
|
|
1890
|
+
return claimsGesture(
|
|
1891
|
+
this.gestureClaims,
|
|
1892
|
+
gesture,
|
|
1893
|
+
localDisplayManager.getGestureOwner(),
|
|
1894
|
+
useAppStatusStore.getState().foregroundedPackage,
|
|
1895
|
+
)
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1861
1898
|
private handleSubscribe(packageName: string, payload: Record<string, unknown>, requestId?: string): void {
|
|
1862
1899
|
const app = this.connectedApps.get(packageName)
|
|
1863
1900
|
if (!app) return
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export const MINIAPP_EXIT_NOTICE_MS = 1500
|
|
2
|
+
|
|
3
|
+
interface ExitGestureApp {
|
|
4
|
+
packageName: string
|
|
5
|
+
name: string
|
|
6
|
+
running: boolean
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface ExitGestureState {
|
|
10
|
+
apps: ExitGestureApp[]
|
|
11
|
+
foregroundedPackage: string | null
|
|
12
|
+
clearForeground: () => void
|
|
13
|
+
stop: (packageName: string) => Promise<void>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ExitGestureDependencies {
|
|
17
|
+
getState: () => ExitGestureState
|
|
18
|
+
getVisiblePackageName: () => string | null
|
|
19
|
+
beginExitNotice: (packageName: string, text: string) => boolean
|
|
20
|
+
finishExitNotice: (packageName: string) => void
|
|
21
|
+
cancelExitNotice: (packageName: string) => void
|
|
22
|
+
setTimeout: (callback: () => void, delay: number) => number
|
|
23
|
+
clearTimeout: (timerId: number) => void
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Owns the short double-tap → exit-notice → normal-stop handoff. */
|
|
27
|
+
export class MiniappExitGesture {
|
|
28
|
+
private pending: {packageName: string; timerId: number} | null = null
|
|
29
|
+
|
|
30
|
+
public constructor(private readonly deps: ExitGestureDependencies) {}
|
|
31
|
+
|
|
32
|
+
/** Returns true when the system gesture was consumed by a visible miniapp exit. */
|
|
33
|
+
public tryExitVisibleMiniapp(): boolean {
|
|
34
|
+
// Swallow repeat taps while the notice is already counting down.
|
|
35
|
+
if (this.pending) return true
|
|
36
|
+
|
|
37
|
+
const packageName = this.deps.getVisiblePackageName()
|
|
38
|
+
if (!packageName) return false
|
|
39
|
+
|
|
40
|
+
const app = this.deps.getState().apps.find((candidate) => candidate.packageName === packageName)
|
|
41
|
+
if (!app?.running) return false
|
|
42
|
+
|
|
43
|
+
const name = app.name.trim() || packageName
|
|
44
|
+
if (!this.deps.beginExitNotice(packageName, `Exiting ${name}…`)) return false
|
|
45
|
+
|
|
46
|
+
const timerId = this.deps.setTimeout(() => {
|
|
47
|
+
const state = this.deps.getState()
|
|
48
|
+
if (state.foregroundedPackage === packageName) state.clearForeground()
|
|
49
|
+
|
|
50
|
+
const stillRunning = state.apps.some((candidate) => candidate.packageName === packageName && candidate.running)
|
|
51
|
+
const stop = stillRunning ? state.stop(packageName) : Promise.resolve()
|
|
52
|
+
void stop
|
|
53
|
+
.catch((error) => {
|
|
54
|
+
console.error(`MiniappExitGesture: failed to stop ${packageName}:`, error)
|
|
55
|
+
})
|
|
56
|
+
.finally(() => {
|
|
57
|
+
if (this.pending?.packageName === packageName) this.pending = null
|
|
58
|
+
// LocalMiniappRuntime.onUnmount normally finishes the notice. This
|
|
59
|
+
// fallback also handles a failed/no-op teardown.
|
|
60
|
+
this.deps.finishExitNotice(packageName)
|
|
61
|
+
})
|
|
62
|
+
}, MINIAPP_EXIT_NOTICE_MS)
|
|
63
|
+
|
|
64
|
+
this.pending = {packageName, timerId}
|
|
65
|
+
return true
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Cancel an in-flight handoff when the device-event router shuts down. */
|
|
69
|
+
public reset(): void {
|
|
70
|
+
if (!this.pending) return
|
|
71
|
+
const {packageName, timerId} = this.pending
|
|
72
|
+
this.pending = null
|
|
73
|
+
this.deps.clearTimeout(timerId)
|
|
74
|
+
this.deps.cancelExitNotice(packageName)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type {OtaCheckCurrentGlassesResult} from "./OtaUpdateCheckService"
|
|
2
|
+
|
|
3
|
+
export const MAX_OTA_AUTO_CHAIN_PASSES = 8
|
|
4
|
+
export const OTA_AUTO_CHAIN_RECONNECT_TIMEOUT_MS = 120_000
|
|
5
|
+
|
|
6
|
+
type OtaAutoChainSession = {
|
|
7
|
+
approvedDowngrade: boolean
|
|
8
|
+
passCount: number
|
|
9
|
+
releaseRange: OtaAutoChainReleaseRange
|
|
10
|
+
reconnectDeadline: number | null
|
|
11
|
+
seenFingerprints: Set<string>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type OtaAutoChainReleaseRange = {
|
|
15
|
+
fromVersion: string | null
|
|
16
|
+
toVersion: string | null
|
|
17
|
+
/** Exact coordinated release identity for the OTA pin, absent for legacy manifests. */
|
|
18
|
+
releaseVersion?: string | null
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type OtaAutoChainAdvanceResult =
|
|
22
|
+
| {advance: true; passCount: number}
|
|
23
|
+
| {advance: false; reason: "inactive" | "duplicate" | "max_passes" | "downgrade_not_approved"}
|
|
24
|
+
|
|
25
|
+
let session: OtaAutoChainSession | null = null
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Identify the exact update offer that produced one OTA pass. Including the
|
|
29
|
+
* observed versions and manifest body lets a changed manifest advance while a
|
|
30
|
+
* stale post-install version_info response is rejected as a duplicate.
|
|
31
|
+
*/
|
|
32
|
+
export function otaAutoChainFingerprint(result: OtaCheckCurrentGlassesResult): string {
|
|
33
|
+
return JSON.stringify({
|
|
34
|
+
manifestUrl: result.manifestUrl ?? "",
|
|
35
|
+
manifestBody: result.manifestBody ?? "",
|
|
36
|
+
buildNumber: result.buildNumber ?? "",
|
|
37
|
+
mtkFirmwareVersion: result.mtkFirmwareVersion ?? "",
|
|
38
|
+
besFirmwareVersion: result.besFirmwareVersion ?? "",
|
|
39
|
+
updates: result.updates,
|
|
40
|
+
targetBuildNumber: result.updateInfo?.versionCode ?? result.latestVersionInfo?.versionCode ?? 0,
|
|
41
|
+
targetBesVersion: result.besVersion ?? "",
|
|
42
|
+
mtkPatch: result.mtkPatch
|
|
43
|
+
? {
|
|
44
|
+
startFirmware: result.mtkPatch.start_firmware,
|
|
45
|
+
endFirmware: result.mtkPatch.end_firmware,
|
|
46
|
+
url: result.mtkPatch.url,
|
|
47
|
+
}
|
|
48
|
+
: null,
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Start a chain after the user explicitly approves its first update pass. */
|
|
53
|
+
export function beginOtaAutoChain(
|
|
54
|
+
initialFingerprint: string,
|
|
55
|
+
approvedDowngrade: boolean,
|
|
56
|
+
releaseRange: OtaAutoChainReleaseRange,
|
|
57
|
+
): void {
|
|
58
|
+
session = {
|
|
59
|
+
approvedDowngrade,
|
|
60
|
+
passCount: 1,
|
|
61
|
+
releaseRange: {...releaseRange},
|
|
62
|
+
reconnectDeadline: null,
|
|
63
|
+
seenFingerprints: new Set([initialFingerprint]),
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function isOtaAutoChainActive(): boolean {
|
|
68
|
+
return session !== null
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function otaAutoChainReleaseRange(): OtaAutoChainReleaseRange | null {
|
|
72
|
+
return session ? {...session.releaseRange} : null
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** End the current chain. Safe to call when no chain is active. */
|
|
76
|
+
export function stopOtaAutoChain(): void {
|
|
77
|
+
session = null
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Start (or continue) the bounded wait for glasses that are rebooting between
|
|
82
|
+
* passes. Re-renders must not extend the original deadline indefinitely.
|
|
83
|
+
*/
|
|
84
|
+
export function otaAutoChainReconnectWaitRemaining(now = performance.now()): number | null {
|
|
85
|
+
if (!session) return null
|
|
86
|
+
|
|
87
|
+
session.reconnectDeadline ??= now + OTA_AUTO_CHAIN_RECONNECT_TIMEOUT_MS
|
|
88
|
+
return Math.max(0, session.reconnectDeadline - now)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** A successful reconnect allows a future pass to establish a fresh wait. */
|
|
92
|
+
export function clearOtaAutoChainReconnectWait(): void {
|
|
93
|
+
if (session) session.reconnectDeadline = null
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Admit the next pass of an active chain. Repeated offers, unexpectedly
|
|
98
|
+
* destructive downgrades, and runaway chains fail closed and end automation.
|
|
99
|
+
*/
|
|
100
|
+
export function tryAdvanceOtaAutoChain(
|
|
101
|
+
fingerprint: string,
|
|
102
|
+
isDowngrade: boolean,
|
|
103
|
+
targetVersion: string | null,
|
|
104
|
+
releaseVersion: string | null = null,
|
|
105
|
+
): OtaAutoChainAdvanceResult {
|
|
106
|
+
if (!session) {
|
|
107
|
+
return {advance: false, reason: "inactive"}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (isDowngrade && !session.approvedDowngrade) {
|
|
111
|
+
stopOtaAutoChain()
|
|
112
|
+
return {advance: false, reason: "downgrade_not_approved"}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (session.seenFingerprints.has(fingerprint)) {
|
|
116
|
+
stopOtaAutoChain()
|
|
117
|
+
return {advance: false, reason: "duplicate"}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (session.passCount >= MAX_OTA_AUTO_CHAIN_PASSES) {
|
|
121
|
+
stopOtaAutoChain()
|
|
122
|
+
return {advance: false, reason: "max_passes"}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
session.seenFingerprints.add(fingerprint)
|
|
126
|
+
session.passCount += 1
|
|
127
|
+
if (targetVersion) session.releaseRange.toVersion = targetVersion
|
|
128
|
+
if (releaseVersion) session.releaseRange.releaseVersion = releaseVersion
|
|
129
|
+
return {advance: true, passCount: session.passCount}
|
|
130
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type {OtaProgress, OtaStatus} from "../facades/ota"
|
|
2
|
+
|
|
3
|
+
export const BES_INSTALL_RESTART_MESSAGE =
|
|
4
|
+
"Restart your glasses to safely exit firmware update mode before trying again"
|
|
5
|
+
|
|
6
|
+
function isDownloadPhaseSnapshot(
|
|
7
|
+
otaStatus: OtaStatus | null | undefined,
|
|
8
|
+
otaProgress: OtaProgress | null | undefined,
|
|
9
|
+
): boolean {
|
|
10
|
+
if (otaStatus?.phase === "download") {
|
|
11
|
+
return true
|
|
12
|
+
}
|
|
13
|
+
if (otaProgress?.stage === "download") {
|
|
14
|
+
return true
|
|
15
|
+
}
|
|
16
|
+
return false
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Offer Change WiFi whenever the OTA flow failed while the glasses were in the
|
|
21
|
+
* download step (any download failure — network, SSL, size cap, verify, etc.).
|
|
22
|
+
*
|
|
23
|
+
* Also covers phone-side watchdog failures that fire while the store still shows
|
|
24
|
+
* an active download phase (stall / global timeout mid-download).
|
|
25
|
+
*/
|
|
26
|
+
export function shouldShowChangeWifiForOtaDownloadFailure(
|
|
27
|
+
otaStatus: OtaStatus | null | undefined,
|
|
28
|
+
otaProgress: OtaProgress | null | undefined,
|
|
29
|
+
localErrorMessage: string,
|
|
30
|
+
): boolean {
|
|
31
|
+
if (otaStatus?.status === "failed" && otaStatus.phase === "download") {
|
|
32
|
+
return true
|
|
33
|
+
}
|
|
34
|
+
if (otaProgress?.status === "FAILED" && otaProgress.stage === "download") {
|
|
35
|
+
return true
|
|
36
|
+
}
|
|
37
|
+
if (localErrorMessage && isDownloadPhaseSnapshot(otaStatus, otaProgress)) {
|
|
38
|
+
return true
|
|
39
|
+
}
|
|
40
|
+
return false
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function getOtaErrorMessage(error?: string): string {
|
|
44
|
+
switch (error) {
|
|
45
|
+
case "no_internet":
|
|
46
|
+
return "Glasses WiFi has no internet connection"
|
|
47
|
+
case "clock_skew":
|
|
48
|
+
return "Glasses clock is wrong — syncing time from your phone, then retrying update check"
|
|
49
|
+
case "ssl_error":
|
|
50
|
+
return "Secure connection failed — try a different WiFi network"
|
|
51
|
+
case "download_failed":
|
|
52
|
+
return "Download failed — check glasses WiFi connection"
|
|
53
|
+
case "firmware_too_large":
|
|
54
|
+
return "Firmware file is unexpectedly large — please contact support"
|
|
55
|
+
case "firmware_verify_failed":
|
|
56
|
+
return "Firmware verification failed — please try again or contact support"
|
|
57
|
+
case "apk_verify_failed":
|
|
58
|
+
return "Update verification failed — please try again or contact support"
|
|
59
|
+
case "install_failed":
|
|
60
|
+
return "Install failed — please try again"
|
|
61
|
+
default:
|
|
62
|
+
return error || "Update failed"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Once a BES install has started, any failure conservatively requires a glasses restart. This
|
|
68
|
+
* avoids adding a BES-specific error code to the wire protocol and, more importantly, never offers
|
|
69
|
+
* an unsafe retry when a generic failure or silence may mean BES entered its raw OTA parser.
|
|
70
|
+
*/
|
|
71
|
+
export function shouldRequireGlassesRebootForBesFailure(
|
|
72
|
+
otaStatus: OtaStatus | null | undefined,
|
|
73
|
+
_otaProgress: OtaProgress | null | undefined,
|
|
74
|
+
localErrorMessage: string,
|
|
75
|
+
): boolean {
|
|
76
|
+
if (otaStatus?.stepType !== "bes" || otaStatus.phase !== "install") {
|
|
77
|
+
return false
|
|
78
|
+
}
|
|
79
|
+
return otaStatus.status === "failed" || Boolean(localErrorMessage)
|
|
80
|
+
}
|