@mentra/engine 3.4.0-dev.971 → 3.4.0-dev.973

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mentra/engine",
3
- "version": "3.4.0-dev.971",
3
+ "version": "3.4.0-dev.973",
4
4
  "description": "Mentra Engine — miniapp registry, WebView bridge, and miniapp store",
5
5
  "main": "build/index.js",
6
6
  "react-native": "src/index.ts",
@@ -97,13 +97,13 @@
97
97
  "registry": "https://registry.npmjs.org/"
98
98
  },
99
99
  "dependencies": {
100
- "@mentra/acs-meeting": "3.4.0-dev.971",
101
- "@mentra/glasses-media": "3.4.0-dev.971",
102
- "@mentra/bluetooth-sdk": "3.4.0-dev.971",
103
- "@mentra/cloud-client": "3.4.0-dev.971",
104
- "@mentra/cloud-protocol": "3.4.0-dev.971",
105
- "@mentra/crust": "3.4.0-dev.971",
106
- "@mentra/miniapp": "3.4.0-dev.971",
100
+ "@mentra/acs-meeting": "3.4.0-dev.973",
101
+ "@mentra/glasses-media": "3.4.0-dev.973",
102
+ "@mentra/bluetooth-sdk": "3.4.0-dev.973",
103
+ "@mentra/cloud-client": "3.4.0-dev.973",
104
+ "@mentra/cloud-protocol": "3.4.0-dev.973",
105
+ "@mentra/crust": "3.4.0-dev.973",
106
+ "@mentra/miniapp": "3.4.0-dev.973",
107
107
  "buffer": "^6.0.3",
108
108
  "events": "^3.3.0",
109
109
  "jszip": "^3.10.1",
@@ -12,9 +12,9 @@ export interface EngineReleaseMetadata {
12
12
  export const ENGINE_RELEASE_METADATA: Readonly<EngineReleaseMetadata> = Object.freeze({
13
13
  "schemaVersion": 1,
14
14
  "familyBaseVersion": "3.4.0",
15
- "releaseIdentity": "3.4.0-dev.971",
16
- "releaseSetId": "mentra-3.4.0-dev.971",
17
- "sourceCommit": "c72b7ecf63112a98579639fc4fdb126aedbf425d",
18
- "otaManifestUrl": "https://artifactscdn.mentraglass.com/Mentra-Community/MentraOS/releases/mentra-builds-v3.4.0/mentra-live-ota-3.4.0-dev.971.json",
19
- "otaManifestSha256": "5be88fa1406efdd5024b94ae18c9124f33ed1cd8e1661f5bcfbabc522a120037"
15
+ "releaseIdentity": "3.4.0-dev.973",
16
+ "releaseSetId": "mentra-3.4.0-dev.973",
17
+ "sourceCommit": "fd4df756e0e979c229fee7ee145db7f7ee2ed1b4",
18
+ "otaManifestUrl": "https://artifactscdn.mentraglass.com/Mentra-Community/MentraOS/releases/mentra-builds-v3.4.0/mentra-live-ota-3.4.0-dev.973.json",
19
+ "otaManifestSha256": "79f09b7f9267d33febe4346b9aae9595c14cbff97caf661ee301e582b4bbbb66"
20
20
  })
@@ -79,8 +79,6 @@ const ENGLISH_COPY: Record<string, string> = {
79
79
  "ota:startingHotspotUpdate": "Starting update…",
80
80
  "ota:transferringToGlasses": "Transferring update to glasses…",
81
81
  "ota:installingOnGlasses": "Installing update on glasses…",
82
- "ota:transportHotspot": "Using glasses hotspot",
83
- "ota:transportWifi": "Using Wi-Fi",
84
82
  "ota:componentApk": "Glasses software",
85
83
  "ota:componentMtk": "System firmware",
86
84
  "ota:componentBes": "Bluetooth firmware",
@@ -242,36 +240,12 @@ export function MentraLiveOtaPreview({
242
240
  }
243
241
 
244
242
  function OtaFlowFrame({allowDevSkip, colors, controller, deviceName, style, superMode, translate}: OtaFlowFrameProps) {
245
- const {state} = controller
246
- const transportLabel =
247
- state.transport &&
248
- [
249
- "starting",
250
- "preparing_hotspot",
251
- "updating",
252
- "restarting",
253
- "verifying",
254
- "complete",
255
- "failed",
256
- "disconnected",
257
- ].includes(state.screen)
258
- ? translate(state.transport === "hotspot" ? "ota:transportHotspot" : "ota:transportWifi")
259
- : null
260
-
261
243
  return (
262
244
  <SafeAreaView style={[styles.safeArea, {backgroundColor: colors.background}, style]}>
263
245
  <View style={styles.header}>
264
246
  <View />
265
247
  <MentraMark color={colors.primary} />
266
248
  </View>
267
- {transportLabel ? (
268
- <Text
269
- accessibilityLabel={transportLabel}
270
- style={[styles.transport, {color: colors.textDim}]}
271
- testID={`ota.transport.${state.transport}`}>
272
- {transportLabel}
273
- </Text>
274
- ) : null}
275
249
  <OtaFlowContent
276
250
  allowDevSkip={allowDevSkip}
277
251
  colors={colors}
@@ -369,7 +343,7 @@ function OtaFlowContent({
369
343
  <>
370
344
  <FlowButton
371
345
  colors={colors}
372
- disabled={!state.wifiStatusKnown}
346
+ disabled={state.screen === "wifi_required" ? !state.wifiStatusKnown : !state.canInstall}
373
347
  label={translate(state.screen === "wifi_required" ? "ota:setupWifi" : "ota:updateNow")}
374
348
  onPress={state.screen === "wifi_required" ? controller.openWifiSetup : controller.install}
375
349
  />
@@ -924,7 +898,6 @@ const styles = StyleSheet.create({
924
898
  svgIcon: {alignItems: "center", height: 72, justifyContent: "center", width: 72},
925
899
  title: {fontSize: 20, fontWeight: "600", textAlign: "center"},
926
900
  body: {fontSize: 14, lineHeight: 20, maxWidth: 420, textAlign: "center"},
927
- transport: {fontSize: 12, lineHeight: 16, textAlign: "center"},
928
901
  errorCode: {fontSize: 12, fontVariant: ["tabular-nums"], lineHeight: 16, opacity: 0.7, textAlign: "center"},
929
902
  percent: {fontSize: 30, fontVariant: ["tabular-nums"], fontWeight: "700"},
930
903
  progressTrack: {borderRadius: 4, height: 8, maxWidth: 420, overflow: "hidden", width: "100%"},
@@ -325,7 +325,7 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
325
325
  return false
326
326
  if (installActionPendingRef.current || !isOtaAutoChainActive() || !result.updateInfo) return false
327
327
  const snapshot = ota.snapshot()
328
- if (!snapshot.wifiStatusKnown || (!snapshot.wifiConnected && snapshot.hotspotOtaVersion !== 1)) return false
328
+ if (snapshot.hotspotOtaVersion !== 1 && (!snapshot.wifiStatusKnown || !snapshot.wifiConnected)) return false
329
329
 
330
330
  const admission = tryAdvanceOtaAutoChain(
331
331
  otaAutoChainFingerprint(result),
@@ -484,7 +484,7 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
484
484
  const fromVersion = currentReleaseVersion(ota.snapshot().appVersion)
485
485
  setOfferedReleaseTransition(result.releaseVersion ? {fromVersion, toVersion: result.releaseVersion} : null)
486
486
  }
487
- if (isOtaAutoChainActive() && !ota.snapshot().wifiStatusKnown) {
487
+ if (isOtaAutoChainActive() && !ota.snapshot().wifiStatusKnown && ota.snapshot().hotspotOtaVersion !== 1) {
488
488
  checkCompletedRef.current = true
489
489
  return
490
490
  }
@@ -534,7 +534,7 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
534
534
  !runtimeReady ||
535
535
  page !== "check" ||
536
536
  (checkState !== "checking" && checkState !== "update_available") ||
537
- !otaSnapshot.wifiStatusKnown ||
537
+ (!otaSnapshot.wifiStatusKnown && otaSnapshot.hotspotOtaVersion !== 1) ||
538
538
  !isOtaAutoChainActive()
539
539
  ) {
540
540
  return
@@ -624,7 +624,7 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
624
624
  setBatteryBlocked(true)
625
625
  return
626
626
  }
627
- if (!snapshot.wifiStatusKnown) {
627
+ if (!snapshot.wifiStatusKnown && snapshot.hotspotOtaVersion !== 1) {
628
628
  check()
629
629
  return
630
630
  }
@@ -667,6 +667,7 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
667
667
  const finish = useCallback(async () => {
668
668
  if (deviceRevision !== otaDeviceSessionRevision()) return
669
669
  if (page === "check") {
670
+ stopOtaAutoChain()
670
671
  onFinishedRef.current?.()
671
672
  return
672
673
  }
@@ -702,7 +703,7 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
702
703
 
703
704
  const state = useMemo<MentraLiveOtaState>(() => {
704
705
  const hotspotSupported = otaSnapshot.hotspotOtaVersion === 1
705
- const canInstall = otaSnapshot.wifiStatusKnown && (otaSnapshot.wifiConnected || hotspotSupported)
706
+ const canInstall = hotspotSupported || (otaSnapshot.wifiStatusKnown && otaSnapshot.wifiConnected)
706
707
  const autoChainActive = isOtaAutoChainActive()
707
708
  if (!runtimeReady) {
708
709
  return {
@@ -776,7 +777,7 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
776
777
  screen,
777
778
  connected: otaSnapshot.connected,
778
779
  batteryLevel: otaSnapshot.batteryLevel,
779
- transport: otaSnapshot.wifiConnected ? "wifi" : hotspotSupported ? "hotspot" : null,
780
+ transport: hotspotSupported ? "hotspot" : otaSnapshot.wifiConnected ? "wifi" : null,
780
781
  updateRequired: isUpdateRequired,
781
782
  versionChange: isVersionChange,
782
783
  versionChangeConverged: false,
@@ -896,7 +897,7 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
896
897
  canFinish: screen === "complete" || (screen === "failed" && requiresGlassesReboot),
897
898
  canDismiss: false,
898
899
  canDiscard: screen === "disconnected",
899
- canOpenWifiSetup: screen === "failed" && showChangeWifi,
900
+ canOpenWifiSetup: screen === "failed" && !hotspotSupported && showChangeWifi,
900
901
  continueDisabled: installSnapshot.continueButtonDisabled,
901
902
  completedUpdate: false,
902
903
  releaseTransition: releaseTransitionFromRange(otaAutoChainReleaseRange()),
@@ -27,7 +27,7 @@ import {waitForOtaInternet} from "./OtaInternetRecovery"
27
27
  import {submitOtaFailureReport, type OtaFailureReason} from "./OtaFailureReportService"
28
28
  import type {OtaArtifactDownloadProgress} from "./OtaArtifactDownloader"
29
29
  import {otaDeviceSessionRevision, subscribeOtaDeviceSession} from "./OtaDeviceSession"
30
- import {isOtaCheckForCurrentDevice} from "./OtaUpdateCheckService"
30
+ import {checkCurrentGlassesForUpdate, isOtaCheckForCurrentDevice} from "./OtaUpdateCheckService"
31
31
  import type {OtaCheckCurrentGlassesResult} from "./OtaUpdateCheckService"
32
32
  import {deriveDisplayState, type DisplayState} from "./otaDisplayState"
33
33
  import {
@@ -325,17 +325,17 @@ class OtaInstallCoordinator {
325
325
  prepare(checkResult: OtaCheckCurrentGlassesResult): "wifi" | "hotspot" {
326
326
  if (!isOtaCheckForCurrentDevice(checkResult)) throw new Error("The checked glasses have changed")
327
327
  const state = useGlassesStore.getState()
328
- if (!state.wifiStatusKnown) {
328
+ const hotspotSupported = state.hotspotOtaVersion === 1
329
+ if (!hotspotSupported && !state.wifiStatusKnown) {
329
330
  throw new Error("Glasses Wi-Fi status is not available")
330
331
  }
331
- const hotspotSupported = state.hotspotOtaVersion === 1
332
332
  const wifiConnected = state.wifi.state === "connected"
333
333
  if (!wifiConnected && !hotspotSupported) {
334
334
  throw new Error("Connected glasses require Wi-Fi for OTA")
335
335
  }
336
336
  this.reportAttemptId = `${Date.now()}-${++this.reportAttemptSequence}`
337
337
  this.preparedCheckResult = checkResult
338
- this.selectedTransport = wifiConnected ? "wifi" : "hotspot"
338
+ this.selectedTransport = hotspotSupported ? "hotspot" : "wifi"
339
339
  this.observedTransport = this.selectedTransport
340
340
  this.hotspotManifestUrl = null
341
341
  this.hotspotPhase = "idle"
@@ -345,6 +345,18 @@ class OtaInstallCoordinator {
345
345
  return this.selectedTransport
346
346
  }
347
347
 
348
+ private initializeCheckedInstall(selected: OtaCheckCurrentGlassesResult["updateInfo"]): void {
349
+ const target =
350
+ selected?.isDowngrade && typeof selected.versionCode === "number" && selected.versionCode > 0
351
+ ? selected.versionCode
352
+ : null
353
+ this.versionChangeSession = target !== null
354
+ this.versionChangeTarget = target
355
+ if (target !== null) {
356
+ console.log(`[OTA_PROGRESS] version-change (downgrade) session, target=${target}`)
357
+ }
358
+ }
359
+
348
360
  /**
349
361
  * Bind the state machine to a mounted progress screen. Idempotent per
350
362
  * attach/detach cycle. Runs the initial-mount arbitration (send ota_start
@@ -364,11 +376,7 @@ class OtaInstallCoordinator {
364
376
  // A late completion from the previous OTA step can clear the store's offer
365
377
  // between check and mount. Keep this install bound to its prepared check.
366
378
  const selected = this.preparedCheckResult ? this.preparedCheckResult.updateInfo : initialState.otaUpdateAvailable
367
- if (selected?.isDowngrade && typeof selected.versionCode === "number" && selected.versionCode > 0) {
368
- this.versionChangeSession = true
369
- this.versionChangeTarget = selected.versionCode
370
- console.log(`[OTA_PROGRESS] version-change (downgrade) session, target=${selected.versionCode}`)
371
- }
379
+ this.initializeCheckedInstall(selected)
372
380
  const recoveryTarget = this.otaStartOwnership?.versionChangeTarget
373
381
  if (this.otaStartOwnership?.outcome === "acknowledged" && recoveryTarget !== undefined) {
374
382
  // The progress route is an observer of this acknowledged install. A remount
@@ -1648,6 +1656,9 @@ class OtaInstallCoordinator {
1648
1656
  if (existingOwnership?.outcome === "rejected" && !this.prepareFreshOtaStartAttempt()) {
1649
1657
  return Promise.resolve()
1650
1658
  }
1659
+ // Capability is cleared on link loss before version_info returns. Preserve
1660
+ // hotspot selection for these paired glasses; the device boundary resets it.
1661
+ if (useGlassesStore.getState().hotspotOtaVersion === 1) this.selectedTransport = "hotspot"
1651
1662
  const ownership: OtaStartOwnership = {
1652
1663
  outcome: "pending",
1653
1664
  promise: Promise.resolve(),
@@ -1674,7 +1685,20 @@ class OtaInstallCoordinator {
1674
1685
  if (this.selectedTransport === "hotspot") {
1675
1686
  if (!this.hotspotManifestUrl) {
1676
1687
  if (!this.preparedCheckResult) {
1677
- throw new Error("No selected OTA check is available for hotspot staging")
1688
+ const result = await checkCurrentGlassesForUpdate()
1689
+ if (!isCurrentAttempt()) return
1690
+ if (!result.hasCheckCompleted || !result.updateAvailable || !result.manifestBody) {
1691
+ throw new Error("No selected OTA check is available for hotspot staging")
1692
+ }
1693
+ this.preparedCheckResult = result
1694
+ this.initializeCheckedInstall(result.updateInfo)
1695
+ // The recovery check can reveal a downgrade after the start attempt
1696
+ // was created. Give that detour its full bounded recovery deadline.
1697
+ if (this.versionChangeSession) {
1698
+ ownership.deadlineAt = Date.now() + LEGACY_GLOBAL_OTA_TIMEOUT_MS
1699
+ this.clearGlobalTimeout()
1700
+ this.maybeStartGlobalTimeout()
1701
+ }
1678
1702
  }
1679
1703
  const manifestUrl = await hotspotOtaTransport.prepare(this.preparedCheckResult, (progress) => {
1680
1704
  if (!isCurrentAttempt()) return