@glassly/engine 0.1.0-dev.1 → 0.1.0-dev.27
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 +53 -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 +23 -16
- package/build/engine.d.ts.map +1 -1
- package/build/engine.js +9 -3
- package/build/engine.js.map +1 -1
- package/build/facades/dev.d.ts +1 -1
- package/build/facades/display.d.ts +1 -1
- package/build/facades/display.d.ts.map +1 -1
- package/build/facades/display.js +1 -1
- package/build/facades/display.js.map +1 -1
- package/build/facades/glasses.d.ts +5 -13
- package/build/facades/glasses.d.ts.map +1 -1
- package/build/facades/glasses.js +5 -3
- package/build/facades/glasses.js.map +1 -1
- package/build/facades/ota.d.ts +19 -21
- package/build/facades/ota.d.ts.map +1 -1
- package/build/facades/ota.js +23 -9
- 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/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 +7 -4
- package/build/index.d.ts.map +1 -1
- package/build/index.js +5 -3
- package/build/index.js.map +1 -1
- package/build/react/MentraLiveOtaFlow.d.ts +39 -0
- package/build/react/MentraLiveOtaFlow.d.ts.map +1 -0
- package/build/react/MentraLiveOtaFlow.js +200 -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 +80 -0
- package/build/react/useMentraLiveOta.d.ts.map +1 -0
- package/build/react/useMentraLiveOta.js +550 -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 +61 -0
- package/build/services/CustomDashboardService.d.ts.map +1 -0
- package/build/services/CustomDashboardService.js +552 -0
- package/build/services/CustomDashboardService.js.map +1 -0
- package/build/services/DeviceEventRouter.d.ts.map +1 -1
- package/build/services/DeviceEventRouter.js +44 -3
- package/build/services/DeviceEventRouter.js.map +1 -1
- package/build/services/GestureDotsService.d.ts +35 -0
- package/build/services/GestureDotsService.d.ts.map +1 -0
- package/build/services/GestureDotsService.js +149 -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 +43 -1
- package/build/services/LocalDisplayManager.d.ts.map +1 -1
- package/build/services/LocalDisplayManager.js +128 -2
- 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 +24 -3
- package/build/services/OtaInstallCoordinator.d.ts.map +1 -1
- package/build/services/OtaInstallCoordinator.js +17 -12
- 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/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/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 +12 -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/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 +1 -1
- package/build/services/otaInstallPolicy.d.ts.map +1 -1
- 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/glasses.d.ts +10 -4
- package/build/stores/glasses.d.ts.map +1 -1
- package/build/stores/glasses.js.map +1 -1
- package/build/stores/settings.d.ts.map +1 -1
- package/build/stores/settings.js +87 -3
- 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 +0 -3
- package/build/types/applet.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.map +1 -1
- package/build/types/index.d.ts +2 -6
- package/build/types/index.d.ts.map +1 -1
- package/build/types/index.js +2 -6
- package/build/types/index.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 +65 -33
- 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 +9 -3
- package/src/facades/display.ts +1 -1
- package/src/facades/glasses.ts +6 -4
- package/src/facades/ota.ts +33 -10
- package/src/facades/pairing.ts +103 -23
- 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 +29 -4
- package/src/react/MentraLiveOtaFlow.tsx +569 -0
- package/src/react/index.ts +21 -0
- package/src/react/useEngineSnapshot.ts +16 -0
- package/src/react/useMentraLiveOta.ts +707 -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 +575 -0
- package/src/services/DeviceEventRouter.ts +47 -5
- package/src/services/GestureDotsService.ts +147 -0
- package/src/services/GlassesStatusProjection.ts +20 -11
- package/src/services/HotspotOtaTransport.ts +8 -7
- package/src/services/LocalDisplayManager.ts +130 -2
- 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 +19 -13
- package/src/services/OtaService.ts +1 -2
- package/src/services/OtaUpdateCheckService.ts +33 -19
- package/src/services/asg/localNetworkTransport.ts +20 -19
- package/src/services/gestureClaims.ts +28 -0
- package/src/services/gestureDotGlyphs.ts +14 -0
- package/src/services/glassesClockSync.ts +5 -1
- package/src/services/otaDisplayState.ts +1 -1
- package/src/services/otaInstallPolicy.ts +1 -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/glasses.ts +13 -6
- package/src/stores/settings.ts +87 -3
- package/src/types/applet.ts +2 -0
- package/src/types/enums.ts +1 -9
- package/src/types/hardware.ts +1 -1
- package/src/types/index.ts +2 -6
- 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
|
@@ -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
|
+
}
|
|
@@ -17,8 +17,7 @@
|
|
|
17
17
|
* state written mid-pass queues a follow-up pass — the same way a setState
|
|
18
18
|
* during React effects scheduled a re-render after the current effect batch.
|
|
19
19
|
*/
|
|
20
|
-
import BluetoothSdk from "@glassly/bluetooth-sdk
|
|
21
|
-
import type {OtaProgress, OtaStatus} from "@glassly/bluetooth-sdk/internal"
|
|
20
|
+
import BluetoothSdk, {type OtaProgress, type OtaStatus} from "@glassly/bluetooth-sdk"
|
|
22
21
|
import GlobalEventEmitter from "../utils/GlobalEventEmitter"
|
|
23
22
|
import {isGlassesConnected, useGlassesStore} from "../stores/glasses"
|
|
24
23
|
import {resolveOtaManifestUrl} from "./otaManifestUrl"
|
|
@@ -170,6 +169,8 @@ export interface OtaInstallSnapshot {
|
|
|
170
169
|
/** Pre-ota_start phone staging/join state for a hotspot attempt. */
|
|
171
170
|
hotspotPhase: HotspotOtaPhase
|
|
172
171
|
hotspotArtifactPercent: number | null
|
|
172
|
+
/** Transport selected from the checked glasses capabilities and Wi-Fi state. */
|
|
173
|
+
transport: "wifi" | "hotspot"
|
|
173
174
|
}
|
|
174
175
|
|
|
175
176
|
type OtaStartOutcome = "pending" | "acknowledged" | "rejected"
|
|
@@ -407,7 +408,7 @@ class OtaInstallCoordinator {
|
|
|
407
408
|
if (this.otaStartOwnership?.outcome === "pending") {
|
|
408
409
|
void this.sendOtaStartWithWatchdogs()
|
|
409
410
|
} else {
|
|
410
|
-
void BluetoothSdk.
|
|
411
|
+
void BluetoothSdk.queryOtaStatus().catch(() => {})
|
|
411
412
|
this.armQueryReplyFallback("retry")
|
|
412
413
|
}
|
|
413
414
|
}
|
|
@@ -449,16 +450,17 @@ class OtaInstallCoordinator {
|
|
|
449
450
|
* stays in the screen): clear the selected update, and after an APK step
|
|
450
451
|
* clear the stale build number so the next check re-reads version_info.
|
|
451
452
|
*/
|
|
452
|
-
finish(): void {
|
|
453
|
+
async finish(): Promise<void> {
|
|
453
454
|
if (this.otaStartOwnership?.outcome !== "pending") {
|
|
454
455
|
this.otaStartOwnership = null
|
|
455
456
|
}
|
|
456
457
|
useGlassesStore.getState().setOtaUpdateAvailable(null)
|
|
457
458
|
if (this.apkStepSeen) {
|
|
458
|
-
BluetoothSdk.updateGlasses({buildNumber: ""})
|
|
459
459
|
useGlassesStore.getState().setGlassesInfo({buildNumber: ""})
|
|
460
460
|
}
|
|
461
|
-
|
|
461
|
+
// The post-install check needs internet again. Do not let callers leave the
|
|
462
|
+
// terminal screen while Android still owns the no-internet hotspot route.
|
|
463
|
+
await this.teardownHotspotTransport()
|
|
462
464
|
}
|
|
463
465
|
|
|
464
466
|
/**
|
|
@@ -467,8 +469,8 @@ class OtaInstallCoordinator {
|
|
|
467
469
|
* deriveDisplayState doesn't resurrect the stale install when the host
|
|
468
470
|
* navigates away and back through /ota routes.
|
|
469
471
|
*/
|
|
470
|
-
discard(): void {
|
|
471
|
-
this.finish()
|
|
472
|
+
async discard(): Promise<void> {
|
|
473
|
+
await this.finish()
|
|
472
474
|
const store = useGlassesStore.getState()
|
|
473
475
|
store.setOtaStatus(null)
|
|
474
476
|
store.setOtaProgress(null)
|
|
@@ -505,6 +507,7 @@ class OtaInstallCoordinator {
|
|
|
505
507
|
versionChangePhase: this.deriveVersionChangePhase(connected),
|
|
506
508
|
hotspotPhase: this.hotspotPhase,
|
|
507
509
|
hotspotArtifactPercent: this.hotspotArtifactPercent,
|
|
510
|
+
transport: this.selectedTransport,
|
|
508
511
|
}
|
|
509
512
|
}
|
|
510
513
|
|
|
@@ -1065,7 +1068,7 @@ class OtaInstallCoordinator {
|
|
|
1065
1068
|
if (this.otaStartOwnership?.outcome === "pending") {
|
|
1066
1069
|
void this.sendOtaStartWithWatchdogs()
|
|
1067
1070
|
}
|
|
1068
|
-
void BluetoothSdk.
|
|
1071
|
+
void BluetoothSdk.queryOtaStatus().catch(() => {})
|
|
1069
1072
|
this.armQueryReplyFallback("reconnect")
|
|
1070
1073
|
return true
|
|
1071
1074
|
}
|
|
@@ -1109,7 +1112,7 @@ class OtaInstallCoordinator {
|
|
|
1109
1112
|
if (noSessionYet) {
|
|
1110
1113
|
if (!isIdleStatus && this.otaStartOwnership?.outcome === "acknowledged") {
|
|
1111
1114
|
console.log("[OTA_PROGRESS] initial mount, acknowledged session has no cached status — reconciling")
|
|
1112
|
-
void BluetoothSdk.
|
|
1115
|
+
void BluetoothSdk.queryOtaStatus().catch(() => {})
|
|
1113
1116
|
this.armQueryReplyFallback("initial-mount")
|
|
1114
1117
|
return
|
|
1115
1118
|
}
|
|
@@ -1125,7 +1128,7 @@ class OtaInstallCoordinator {
|
|
|
1125
1128
|
void this.sendOtaStartWithWatchdogs()
|
|
1126
1129
|
} else {
|
|
1127
1130
|
console.log("[OTA_PROGRESS] initial mount, session exists, sending ota_query_status")
|
|
1128
|
-
void BluetoothSdk.
|
|
1131
|
+
void BluetoothSdk.queryOtaStatus().catch(() => {})
|
|
1129
1132
|
this.armQueryReplyFallback("initial-mount")
|
|
1130
1133
|
}
|
|
1131
1134
|
}
|
|
@@ -1154,7 +1157,7 @@ class OtaInstallCoordinator {
|
|
|
1154
1157
|
this.clearProgressTimeout()
|
|
1155
1158
|
this.onFirstActivity()
|
|
1156
1159
|
this.onFirstNonZeroProgress()
|
|
1157
|
-
void BluetoothSdk.
|
|
1160
|
+
void BluetoothSdk.queryOtaStatus().catch(() => {})
|
|
1158
1161
|
useGlassesStore.getState().setMtkUpdatedThisSession(true)
|
|
1159
1162
|
}
|
|
1160
1163
|
|
|
@@ -1345,7 +1348,7 @@ class OtaInstallCoordinator {
|
|
|
1345
1348
|
const s = useGlassesStore.getState().otaStatus
|
|
1346
1349
|
if (s?.stepType === "apk" && s.phase === "install" && s.status === "in_progress") {
|
|
1347
1350
|
this.apkInstallPollInFlight = true
|
|
1348
|
-
void BluetoothSdk.
|
|
1351
|
+
void BluetoothSdk.queryOtaStatus()
|
|
1349
1352
|
.catch(() => {})
|
|
1350
1353
|
.finally(() => {
|
|
1351
1354
|
this.apkInstallPollInFlight = false
|
|
@@ -1492,6 +1495,9 @@ class OtaInstallCoordinator {
|
|
|
1492
1495
|
otaVersionUrl = this.hotspotManifestUrl
|
|
1493
1496
|
this.maybeArmStuckWatchdog()
|
|
1494
1497
|
}
|
|
1498
|
+
if (!otaVersionUrl) {
|
|
1499
|
+
throw new Error("OTA is disabled because this build has no immutable manifest pin")
|
|
1500
|
+
}
|
|
1495
1501
|
console.log(`[OTA_PROGRESS] sending ota_start with ${this.selectedTransport} manifest URL: ${otaVersionUrl}`)
|
|
1496
1502
|
nativeStartAttempted = true
|
|
1497
1503
|
await BluetoothSdk.startOtaUpdate(otaVersionUrl)
|
|
@@ -10,8 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Started by `engine.start()`. Idempotent.
|
|
12
12
|
*/
|
|
13
|
-
import BluetoothSdk from "@glassly/bluetooth-sdk
|
|
14
|
-
import type {OtaStatus} from "@glassly/bluetooth-sdk/internal"
|
|
13
|
+
import BluetoothSdk, {type OtaStatus} from "@glassly/bluetooth-sdk"
|
|
15
14
|
import GlobalEventEmitter from "../utils/GlobalEventEmitter"
|
|
16
15
|
import {useGlassesStore} from "../stores/glasses"
|
|
17
16
|
import {handleOtaClockSkewFromGlasses} from "./glassesClockSync"
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import BluetoothSdk from "@glassly/bluetooth-sdk
|
|
2
|
-
import
|
|
1
|
+
import BluetoothSdk, {type OtaUpdateInfo} from "@glassly/bluetooth-sdk"
|
|
2
|
+
import {ENGINE_RELEASE_METADATA} from "../generated/releaseMetadata"
|
|
3
3
|
import {getGlassesSystemTimeMs, isGlassesConnected, useGlassesStore, waitForGlassesState} from "../stores/glasses"
|
|
4
|
-
import {SETTINGS, useSettingsStore} from "../stores/settings"
|
|
5
4
|
import {maybeFixGlassesClockFromVersionInfo} from "./glassesClockSync"
|
|
6
|
-
import {resolveOtaManifestUrl} from "./otaManifestUrl"
|
|
5
|
+
import {hasConfiguredModernOtaManifestPin, resolveOtaManifestUrl} from "./otaManifestUrl"
|
|
6
|
+
import {resolveOtaReleaseVersion} from "./otaReleaseVersion"
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Phone-side mirror of ASG's `DOWNGRADE_FLOOR_VERSION_CODE`. Set to the versionCode of the first
|
|
@@ -39,6 +39,7 @@ export interface BesFirmware {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
export interface VersionJson {
|
|
42
|
+
releaseVersion?: string
|
|
42
43
|
apps?: {
|
|
43
44
|
[packageName: string]: VersionInfo
|
|
44
45
|
}
|
|
@@ -67,6 +68,8 @@ export interface OtaCheckResult {
|
|
|
67
68
|
* instead of racing it with a second fetch of the same URL.
|
|
68
69
|
*/
|
|
69
70
|
manifestBody: string | null
|
|
71
|
+
/** Coordinated release identity for the selected OTA pin. */
|
|
72
|
+
releaseVersion: string | null
|
|
70
73
|
/**
|
|
71
74
|
* Why the check failed, when it did. "network" is retryable (connection/server
|
|
72
75
|
* trouble); "pin_unavailable" is permanent for this app build (manifest 404/gone,
|
|
@@ -107,6 +110,7 @@ function emptyCheckResult(skippedReason?: OtaCheckSkippedReason): OtaCheckCurren
|
|
|
107
110
|
besVersion: null,
|
|
108
111
|
isApkDowngrade: false,
|
|
109
112
|
manifestBody: null,
|
|
113
|
+
releaseVersion: null,
|
|
110
114
|
updateInfo: null,
|
|
111
115
|
isRequired: true,
|
|
112
116
|
skippedReason,
|
|
@@ -123,7 +127,7 @@ async function fetchVersionInfoDetailed(url: string): Promise<ManifestFetchResul
|
|
|
123
127
|
try {
|
|
124
128
|
const response = await fetch(url)
|
|
125
129
|
if (!response.ok) {
|
|
126
|
-
console.
|
|
130
|
+
console.warn("Failed to fetch version info:", response.status)
|
|
127
131
|
// 4xx: the manifest does not exist (or is gone) — permanent for this app
|
|
128
132
|
// build, retrying cannot help. Everything else is transient server/network
|
|
129
133
|
// trouble and retryable.
|
|
@@ -135,11 +139,11 @@ async function fetchVersionInfoDetailed(url: string): Promise<ManifestFetchResul
|
|
|
135
139
|
try {
|
|
136
140
|
return {json: await response.json()}
|
|
137
141
|
} catch (parseError) {
|
|
138
|
-
console.
|
|
142
|
+
console.warn("OTA: manifest is not valid JSON:", parseError)
|
|
139
143
|
return {json: null, failureReason: "pin_unavailable"}
|
|
140
144
|
}
|
|
141
145
|
} catch (error) {
|
|
142
|
-
console.
|
|
146
|
+
console.warn("OTA: Error fetching version info:", error)
|
|
143
147
|
return {json: null, failureReason: "network"}
|
|
144
148
|
}
|
|
145
149
|
}
|
|
@@ -299,6 +303,7 @@ export async function checkForOtaUpdate(
|
|
|
299
303
|
besVersion: null,
|
|
300
304
|
isApkDowngrade: false,
|
|
301
305
|
manifestBody: null,
|
|
306
|
+
releaseVersion: null,
|
|
302
307
|
checkFailureReason: fetched.failureReason ?? "network",
|
|
303
308
|
}
|
|
304
309
|
}
|
|
@@ -330,6 +335,7 @@ export async function checkForOtaUpdate(
|
|
|
330
335
|
besVersion: null,
|
|
331
336
|
isApkDowngrade: false,
|
|
332
337
|
manifestBody: null,
|
|
338
|
+
releaseVersion: null,
|
|
333
339
|
checkFailureReason: "pin_unavailable",
|
|
334
340
|
}
|
|
335
341
|
}
|
|
@@ -368,6 +374,12 @@ export async function checkForOtaUpdate(
|
|
|
368
374
|
besVersion: versionJson?.bes_firmware?.version || null,
|
|
369
375
|
isApkDowngrade: apkDirection === "downgrade",
|
|
370
376
|
manifestBody: versionJson ? JSON.stringify(versionJson) : null,
|
|
377
|
+
releaseVersion: resolveOtaReleaseVersion({
|
|
378
|
+
manifestReleaseVersion: versionJson.releaseVersion,
|
|
379
|
+
manifestUrl: otaVersionUrl,
|
|
380
|
+
packagedManifestUrl: ENGINE_RELEASE_METADATA.otaManifestUrl,
|
|
381
|
+
packagedReleaseIdentity: ENGINE_RELEASE_METADATA.releaseIdentity,
|
|
382
|
+
}),
|
|
371
383
|
}
|
|
372
384
|
} catch (error) {
|
|
373
385
|
console.error("Error checking for OTA update:", error)
|
|
@@ -380,6 +392,7 @@ export async function checkForOtaUpdate(
|
|
|
380
392
|
besVersion: null,
|
|
381
393
|
isApkDowngrade: false,
|
|
382
394
|
manifestBody: null,
|
|
395
|
+
releaseVersion: null,
|
|
383
396
|
checkFailureReason: "network",
|
|
384
397
|
}
|
|
385
398
|
}
|
|
@@ -401,22 +414,19 @@ export async function checkCurrentGlassesForUpdate(
|
|
|
401
414
|
return emptyCheckResult("disconnected")
|
|
402
415
|
}
|
|
403
416
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
const overrideUrl = useSettingsStore.getState().getSetting(SETTINGS.ota_version_url.key)
|
|
407
|
-
const overrideActive = superMode && typeof overrideUrl === "string" && overrideUrl.trim() !== ""
|
|
408
|
-
if (!embeddedManifestUrl && !overrideActive) {
|
|
409
|
-
console.log("OTA: check skipped - mobile app has no embedded OTA manifest pin")
|
|
417
|
+
if (!hasConfiguredModernOtaManifestPin()) {
|
|
418
|
+
console.log("OTA: check skipped - this app or Engine package has no embedded OTA manifest pin")
|
|
410
419
|
return emptyCheckResult("dev_build")
|
|
411
420
|
}
|
|
412
|
-
if (!embeddedManifestUrl) {
|
|
413
|
-
console.log("OTA: no embedded manifest pin, but super-mode manifest override is active - checking anyway")
|
|
414
|
-
}
|
|
415
421
|
|
|
416
422
|
if (refreshVersionInfo) {
|
|
417
|
-
void BluetoothSdk.requestVersionInfo()
|
|
418
|
-
|
|
419
|
-
|
|
423
|
+
void BluetoothSdk.requestVersionInfo()
|
|
424
|
+
.then((versionInfo) => {
|
|
425
|
+
useGlassesStore.getState().setGlassesInfo(versionInfo)
|
|
426
|
+
})
|
|
427
|
+
.catch((error) => {
|
|
428
|
+
console.warn("OTA: Failed to request version_info from glasses:", error)
|
|
429
|
+
})
|
|
420
430
|
}
|
|
421
431
|
|
|
422
432
|
let buildNumber = useGlassesStore.getState().buildNumber
|
|
@@ -470,6 +480,10 @@ export async function checkCurrentGlassesForUpdate(
|
|
|
470
480
|
}
|
|
471
481
|
|
|
472
482
|
const manifestUrl = resolveOtaManifestUrl(useGlassesStore.getState().otaVersionUrl, buildNumber)
|
|
483
|
+
if (!manifestUrl) {
|
|
484
|
+
console.log("OTA: check skipped - no OTA manifest pin resolved")
|
|
485
|
+
return emptyCheckResult("dev_build")
|
|
486
|
+
}
|
|
473
487
|
const result = await checkForOtaUpdate(
|
|
474
488
|
manifestUrl,
|
|
475
489
|
buildNumber,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as RNFS from "@dr.pogodin/react-native-fs"
|
|
2
|
-
import {
|
|
2
|
+
import {otaLocalNetwork, type OtaLocalNetworkDownloadProgress} from "@glassly/bluetooth-sdk/ota-transport"
|
|
3
3
|
import {Buffer} from "buffer"
|
|
4
4
|
import {Platform} from "react-native"
|
|
5
5
|
import WifiManager from "react-native-wifi-reborn"
|
|
@@ -17,7 +17,7 @@ export function shouldUseScopedLocalNetwork(os: string, moduleAvailable: boolean
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
function supportsScopedConnection(): boolean {
|
|
20
|
-
return shouldUseScopedLocalNetwork(Platform.OS,
|
|
20
|
+
return shouldUseScopedLocalNetwork(Platform.OS, otaLocalNetwork.isAvailable())
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
function normalizeHeaders(headers?: HeadersInit): Record<string, string> {
|
|
@@ -48,25 +48,25 @@ export const localNetworkTransport = {
|
|
|
48
48
|
await WifiManager.connectToProtectedSSID(ssid, password, false, false)
|
|
49
49
|
return undefined
|
|
50
50
|
}
|
|
51
|
-
const result = await
|
|
51
|
+
const result = await otaLocalNetwork.connect(ssid, password)
|
|
52
52
|
scopedConnectionActive = true
|
|
53
53
|
return result.localAddress
|
|
54
54
|
},
|
|
55
55
|
|
|
56
56
|
async disconnect(): Promise<void> {
|
|
57
57
|
if (supportsScopedConnection()) {
|
|
58
|
-
await
|
|
58
|
+
await otaLocalNetwork.disconnect().catch(() => {})
|
|
59
59
|
}
|
|
60
60
|
scopedConnectionActive = false
|
|
61
61
|
nativeJobs.clear()
|
|
62
62
|
},
|
|
63
63
|
|
|
64
64
|
async fetch(url: string | URL | Request, init?: RequestInit, timeoutMs = 30_000): Promise<Response> {
|
|
65
|
-
if (!scopedConnectionActive || !
|
|
65
|
+
if (!scopedConnectionActive || !otaLocalNetwork.isAvailable() || typeof url !== "string") {
|
|
66
66
|
return globalThis.fetch(url, init)
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
const nativeModule =
|
|
69
|
+
const nativeModule = otaLocalNetwork
|
|
70
70
|
const id = requestId("request")
|
|
71
71
|
const signal = init?.signal
|
|
72
72
|
const cancel = () => void nativeModule.cancel(id)
|
|
@@ -92,13 +92,13 @@ export const localNetworkTransport = {
|
|
|
92
92
|
},
|
|
93
93
|
|
|
94
94
|
downloadFile(options: DownloadOptions): DownloadHandle {
|
|
95
|
-
if (!scopedConnectionActive || !
|
|
95
|
+
if (!scopedConnectionActive || !otaLocalNetwork.isAvailable()) return RNFS.downloadFile(options)
|
|
96
96
|
|
|
97
97
|
const jobId = nextJobId++
|
|
98
98
|
const id = requestId("download")
|
|
99
99
|
let began = false
|
|
100
100
|
nativeJobs.set(jobId, id)
|
|
101
|
-
const subscription =
|
|
101
|
+
const subscription = otaLocalNetwork.onDownloadProgress((event: OtaLocalNetworkDownloadProgress) => {
|
|
102
102
|
if (event.requestId !== id) return
|
|
103
103
|
if (!began) {
|
|
104
104
|
began = true
|
|
@@ -116,14 +116,15 @@ export const localNetworkTransport = {
|
|
|
116
116
|
})
|
|
117
117
|
})
|
|
118
118
|
|
|
119
|
-
const promise =
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
119
|
+
const promise = otaLocalNetwork
|
|
120
|
+
.download(
|
|
121
|
+
id,
|
|
122
|
+
options.fromUrl,
|
|
123
|
+
options.toFile,
|
|
124
|
+
options.headers || {},
|
|
125
|
+
options.connectionTimeout || 30_000,
|
|
126
|
+
options.readTimeout || 30_000,
|
|
127
|
+
)
|
|
127
128
|
.then((result) => {
|
|
128
129
|
if (!began) {
|
|
129
130
|
options.begin?.({
|
|
@@ -154,16 +155,16 @@ export const localNetworkTransport = {
|
|
|
154
155
|
|
|
155
156
|
stopDownload(jobId: number): void {
|
|
156
157
|
const nativeId = nativeJobs.get(jobId)
|
|
157
|
-
if (nativeId &&
|
|
158
|
+
if (nativeId && otaLocalNetwork.isAvailable()) {
|
|
158
159
|
cancelledNativeJobs.add(jobId)
|
|
159
|
-
void
|
|
160
|
+
void otaLocalNetwork.cancel(nativeId)
|
|
160
161
|
return
|
|
161
162
|
}
|
|
162
163
|
RNFS.stopDownload(jobId)
|
|
163
164
|
},
|
|
164
165
|
}
|
|
165
166
|
|
|
166
|
-
|
|
167
|
+
otaLocalNetwork.onNetworkLost(() => {
|
|
167
168
|
scopedConnectionActive = false
|
|
168
169
|
nativeJobs.clear()
|
|
169
170
|
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gesture-claim arbitration (CLAIM_GESTURES), split out of
|
|
3
|
+
* LocalMiniappRuntime so it is unit-testable — the runtime module itself
|
|
4
|
+
* pulls a native import surface bun can't parse.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* True iff the miniapp in front of the user has claimed `gesture`.
|
|
9
|
+
*
|
|
10
|
+
* "In front" spans both screens. Most claiming apps are glasses-side: the
|
|
11
|
+
* terminal runs as a background miniapp with the phone pocketed, so gating on
|
|
12
|
+
* the phone's Compositor foreground alone left its double_tap claim inert and
|
|
13
|
+
* the system dashboard kept opening mid-session. Glasses ownership is the
|
|
14
|
+
* primary signal; the phone foreground is still honored for apps driven from
|
|
15
|
+
* the handset. A claimer that owns neither screen is background and never
|
|
16
|
+
* hijacks a system gesture.
|
|
17
|
+
*/
|
|
18
|
+
export function claimsGesture(
|
|
19
|
+
claims: ReadonlyMap<string, ReadonlySet<string>>,
|
|
20
|
+
gesture: string,
|
|
21
|
+
glassesOwner: string | null,
|
|
22
|
+
phoneForeground: string | null,
|
|
23
|
+
): boolean {
|
|
24
|
+
for (const pkg of [glassesOwner, phoneForeground]) {
|
|
25
|
+
if (pkg && claims.get(pkg)?.has(gesture) === true) return true
|
|
26
|
+
}
|
|
27
|
+
return false
|
|
28
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED by scripts/gen-gesture-dot-glyphs.mjs — do not edit by hand.
|
|
3
|
+
* 24×24 8-bit grayscale PNGs (white on black) for the gesture-dots indicator.
|
|
4
|
+
*/
|
|
5
|
+
export const GESTURE_DOT_GLYPH_SIZE = 24
|
|
6
|
+
|
|
7
|
+
export type GestureDotGlyph = "dot" | "up" | "down" | "blank"
|
|
8
|
+
|
|
9
|
+
export const GESTURE_DOT_GLYPHS: Record<GestureDotGlyph, string> = {
|
|
10
|
+
dot: "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAAAAADFHGIkAAAALklEQVR4nM2QsQ0AAAjC/P/pmrhqZ+xIF6DqBQyWbwO3gduAmKjQuj7QL/ETEzQmMptl7Q9NBAAAAABJRU5ErkJggg==",
|
|
11
|
+
up: "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAAAAADFHGIkAAAAMElEQVR4nGNgoCv4////f1zi2GT+/4ciTHE4RhdHIlDFUUhkcTQKQx9WBw8PCVoBAIcbOcf3xeeBAAAAAElFTkSuQmCC",
|
|
12
|
+
down: "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAAAAADFHGIkAAAALUlEQVR4nGNgoC/4/3+4SkBFUCkkNjKJIoMg0GRgGEMGgrC4CQSwuxanR2gEAO1/Ocfgp8uvAAAAAElFTkSuQmCC",
|
|
13
|
+
blank: "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAAAAADFHGIkAAAAD0lEQVR4nGNgGAWjgPoAAAJYAAGgxYwDAAAAAElFTkSuQmCC",
|
|
14
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Push phone time to glasses only when clock skew is detected (gallery sync, OTA, etc.).
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import BluetoothSdk from "@glassly/bluetooth-sdk
|
|
5
|
+
import BluetoothSdk from "@glassly/bluetooth-sdk"
|
|
6
6
|
import {BgTimer} from "../utils/timers"
|
|
7
7
|
import {useGlassesStore} from "../stores/glasses"
|
|
8
8
|
|
|
@@ -87,6 +87,10 @@ export async function handleOtaClockSkewFromGlasses(
|
|
|
87
87
|
lastOtaClockFixAt = Date.now()
|
|
88
88
|
const {buildNumber, otaVersionUrl} = useGlassesStore.getState()
|
|
89
89
|
const manifestUrl = resolveOtaManifestUrl(otaVersionUrl, buildNumber)
|
|
90
|
+
if (!manifestUrl) {
|
|
91
|
+
console.warn("[GlassesClockSync] OTA restart skipped because this build has no manifest pin")
|
|
92
|
+
return false
|
|
93
|
+
}
|
|
90
94
|
console.log("[GlassesClockSync] ⏰ Restarting OTA with ota_start after clock fix")
|
|
91
95
|
await BluetoothSdk.startOtaUpdate(manifestUrl)
|
|
92
96
|
return true
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Shared OTA progress watchdog timings (see progress-legacy.tsx / progress.tsx). */
|
|
2
|
-
import type {OtaProgress, OtaStatus} from "@glassly/bluetooth-sdk
|
|
2
|
+
import type {OtaProgress, OtaStatus} from "@glassly/bluetooth-sdk"
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* First glasses `build_number` (ASG `versionCode`) that uses `progress.tsx` with unified
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {OtaProgress, OtaProgressStatus, OtaStatus} from "@glassly/bluetooth-sdk
|
|
1
|
+
import type {OtaProgress, OtaProgressStatus, OtaStatus} from "@glassly/bluetooth-sdk"
|
|
2
2
|
|
|
3
3
|
/** Normalized ota_status fields (snake_case) after bridging Core/expo (snake or camel). */
|
|
4
4
|
export type NormalizedOtaStatusEvent = {
|