@mentra/engine 3.1.0-beta.166 → 3.1.0-beta.170

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.1.0-beta.166",
3
+ "version": "3.1.0-beta.170",
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,11 +97,11 @@
97
97
  "registry": "https://registry.npmjs.org/"
98
98
  },
99
99
  "dependencies": {
100
- "@mentra/bluetooth-sdk": "3.1.0-beta.166",
101
- "@mentra/cloud-client": "3.1.0-beta.166",
102
- "@mentra/cloud-protocol": "3.1.0-beta.166",
103
- "@mentra/crust": "3.1.0-beta.166",
104
- "@mentra/miniapp": "3.1.0-beta.166",
100
+ "@mentra/bluetooth-sdk": "3.1.0-beta.170",
101
+ "@mentra/cloud-client": "3.1.0-beta.170",
102
+ "@mentra/cloud-protocol": "3.1.0-beta.170",
103
+ "@mentra/crust": "3.1.0-beta.170",
104
+ "@mentra/miniapp": "3.1.0-beta.170",
105
105
  "buffer": "^6.0.3",
106
106
  "events": "^3.3.0",
107
107
  "react-native-marked": "8.1.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.1.0",
15
- "releaseIdentity": "3.1.0-beta.166",
16
- "releaseSetId": "mentra-3.1.0-beta.166",
17
- "sourceCommit": "cedbd0ae6a783a7d55e0526fd3a49dcd6f5b01cc",
18
- "otaManifestUrl": "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.1.0/mentra-live-ota-3.1.0-beta.166.json",
19
- "otaManifestSha256": "e125a20beb4c400ecad163d059da65b14390acf809b6f0d074fd874bfd7b59be"
15
+ "releaseIdentity": "3.1.0-beta.170",
16
+ "releaseSetId": "mentra-3.1.0-beta.170",
17
+ "sourceCommit": "4b429bc36e827f56658c1c51f5248209fb05886f",
18
+ "otaManifestUrl": "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.1.0/mentra-live-ota-3.1.0-beta.170.json",
19
+ "otaManifestSha256": "2f3a546e6e8c6339e464b7032274d42d09c48a06111af746ecf8c7af3f583508"
20
20
  })
@@ -70,6 +70,20 @@ const DEFAULT_THEME: MentraLiveOtaFlowTheme = {
70
70
  }
71
71
 
72
72
  const ENGLISH_COPY: Record<string, string> = {
73
+ "ota:downloadingToPhone": "Downloading update to phone...",
74
+ "ota:startingGlassesHotspot": "Starting glasses hotspot...",
75
+ "ota:connectingPhoneToGlasses": "Connecting phone to glasses...",
76
+ "ota:startingHotspotUpdate": "Starting update...",
77
+ "ota:transferringToGlasses": "Transferring update to glasses...",
78
+ "ota:installingOnGlasses": "Installing update on glasses...",
79
+ "ota:componentApk": "Glasses software",
80
+ "ota:componentMtk": "System firmware",
81
+ "ota:componentBes": "Bluetooth firmware",
82
+ "ota:updateFile": "File {{current}} of {{total}} · {{component}}",
83
+ "ota:updatePart": "Update {{current}} of {{total}} · {{component}}",
84
+ "ota:phoneFileProgress": "Each file downloads separately. Progress is for the current file.",
85
+ "ota:transferFileProgress": "Progress is for this file’s transfer from your phone.",
86
+
73
87
  "common:continue": "Continue",
74
88
  "common:done": "Done",
75
89
  "ota:checkingForUpdates": "Checking for updates",
@@ -125,6 +139,12 @@ const ENGLISH_COPY: Record<string, string> = {
125
139
  "Your glasses restarted with new firmware. One more step: they'll now continue to the required version.",
126
140
  }
127
141
 
142
+ const componentCopyKey = {
143
+ apk: "ota:componentApk",
144
+ mtk: "ota:componentMtk",
145
+ bes: "ota:componentBes",
146
+ } as const
147
+
128
148
  function defaultTranslate(key: string, options?: Record<string, string>): string {
129
149
  let value = ENGLISH_COPY[key] ?? key
130
150
  for (const [name, replacement] of Object.entries(options ?? {})) {
@@ -374,28 +394,65 @@ function OtaFlowContent({
374
394
  }
375
395
 
376
396
  if (state.screen === "starting" || state.screen === "preparing_hotspot") {
377
- const title =
397
+ const title = translate(
378
398
  state.hotspotPhase === "downloading"
379
- ? "Downloading update to phone..."
399
+ ? "ota:downloadingToPhone"
380
400
  : state.hotspotPhase === "starting_hotspot"
381
- ? "Starting glasses hotspot..."
401
+ ? "ota:startingGlassesHotspot"
382
402
  : state.hotspotPhase === "joining_hotspot"
383
- ? "Connecting phone to glasses..."
384
- : "Starting update..."
403
+ ? "ota:connectingPhoneToGlasses"
404
+ : "ota:startingHotspotUpdate",
405
+ )
406
+ const artifact = state.hotspotPhase === "downloading" ? state.hotspotArtifact : null
385
407
  return (
386
408
  <FlowPage colors={colors} icon="download" title={title}>
409
+ {artifact ? (
410
+ <BodyText colors={colors}>
411
+ {translate("ota:updateFile", {
412
+ current: String(artifact.index + 1),
413
+ total: String(artifact.totalCount),
414
+ component: translate(componentCopyKey[artifact.kind]),
415
+ })}
416
+ </BodyText>
417
+ ) : null}
387
418
  {state.hotspotPhase === "downloading" && state.hotspotArtifactPercent !== null ? (
388
419
  <PercentText colors={colors} percent={state.hotspotArtifactPercent} />
389
420
  ) : null}
390
421
  <ActivityIndicator size="large" color={colors.foreground} />
422
+ {state.hotspotPhase === "downloading" ? (
423
+ <BodyText colors={colors}>{translate("ota:phoneFileProgress")}</BodyText>
424
+ ) : null}
391
425
  <BodyText colors={colors}>Do not disconnect your glasses</BodyText>
392
426
  </FlowPage>
393
427
  )
394
428
  }
395
429
 
396
430
  if (state.screen === "updating") {
431
+ const hotspot = state.transport === "hotspot"
432
+ const title = hotspot
433
+ ? translate(state.phase === "download" ? "ota:transferringToGlasses" : "ota:installingOnGlasses")
434
+ : state.phase === "download"
435
+ ? "Downloading..."
436
+ : "Installing..."
437
+ const component = state.step ? translate(componentCopyKey[state.step]) : null
438
+ const hasStepCount =
439
+ state.currentStep !== null &&
440
+ state.totalSteps !== null &&
441
+ state.currentStep > 0 &&
442
+ state.currentStep <= state.totalSteps
397
443
  return (
398
- <FlowPage colors={colors} icon="download" title={state.phase === "download" ? "Downloading..." : "Installing..."}>
444
+ <FlowPage colors={colors} icon="download" title={title}>
445
+ {hotspot && component ? (
446
+ <BodyText colors={colors}>
447
+ {hasStepCount
448
+ ? translate("ota:updatePart", {
449
+ current: String(state.currentStep),
450
+ total: String(state.totalSteps),
451
+ component,
452
+ })
453
+ : component}
454
+ </BodyText>
455
+ ) : null}
399
456
  {state.installingApkOnly ? (
400
457
  <ActivityIndicator size="large" color={colors.foreground} />
401
458
  ) : (
@@ -409,6 +466,9 @@ function OtaFlowContent({
409
466
  </>
410
467
  )}
411
468
  <BodyText colors={colors}>Do not disconnect your glasses</BodyText>
469
+ {hotspot && state.phase === "download" ? (
470
+ <BodyText colors={colors}>{translate("ota:transferFileProgress")}</BodyText>
471
+ ) : null}
412
472
  {state.versionChange && state.phase === "install" ? (
413
473
  <BodyText colors={colors}>{translate("ota:downgradeDuration")}</BodyText>
414
474
  ) : null}
@@ -82,6 +82,11 @@ export type MentraLiveOtaState = {
82
82
  hotspotSupported: boolean
83
83
  hotspotPhase: MentraLiveOtaHotspotPhase
84
84
  hotspotArtifactPercent: number | null
85
+ /**
86
+ * Current phone download context. Index is zero-based; percent applies to this file only.
87
+ * Optional for source compatibility; this hook returns null when no file is active.
88
+ */
89
+ hotspotArtifact?: {kind: MentraLiveOtaStep; index: number; totalCount: number} | null
85
90
  phase: MentraLiveOtaInstallPhase | null
86
91
  step: MentraLiveOtaStep | null
87
92
  currentStep: number | null
@@ -624,6 +629,7 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
624
629
  hotspotSupported,
625
630
  hotspotPhase: "idle",
626
631
  hotspotArtifactPercent: null,
632
+ hotspotArtifact: null,
627
633
  phase: null,
628
634
  step: null,
629
635
  currentStep: null,
@@ -678,6 +684,7 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
678
684
  hotspotSupported,
679
685
  hotspotPhase: "idle",
680
686
  hotspotArtifactPercent: null,
687
+ hotspotArtifact: null,
681
688
  phase: null,
682
689
  step: null,
683
690
  currentStep: null,
@@ -732,6 +739,7 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
732
739
  }
733
740
  : null
734
741
  const totalSteps = installSnapshot.otaStatus?.totalSteps ?? null
742
+ const artifact = installSnapshot.hotspotArtifact
735
743
  const changelogs = screen === "complete" ? releaseChangelogsForActiveChain() : []
736
744
  return {
737
745
  screen,
@@ -747,6 +755,7 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
747
755
  hotspotSupported,
748
756
  hotspotPhase: installSnapshot.hotspotPhase,
749
757
  hotspotArtifactPercent: installSnapshot.hotspotArtifactPercent,
758
+ hotspotArtifact: artifact ? {kind: artifact.kind, index: artifact.index, totalCount: artifact.totalCount} : null,
750
759
  phase: installSnapshot.otaStatus?.phase ?? installSnapshot.otaProgress?.stage ?? null,
751
760
  step: installSnapshot.otaStatus?.stepType ?? null,
752
761
  currentStep: installSnapshot.otaStatus?.currentStep ?? null,
@@ -139,6 +139,16 @@ export async function prepareArtifacts(
139
139
  const prepared: PreparedOtaArtifact[] = []
140
140
  for (let index = 0; index < plan.length; index++) {
141
141
  const entry = plan[index]
142
+ // Announce each file before cache verification or native download callbacks,
143
+ // so the previous file's 100% cannot linger under the next file's label.
144
+ onProgress?.({
145
+ kind: entry.kind,
146
+ index,
147
+ totalCount: plan.length,
148
+ artifactPercent: 0,
149
+ bytesWritten: 0,
150
+ contentLength: 0,
151
+ })
142
152
 
143
153
  const cachedPath = `${directory}/${entry.sha256}`
144
154
  if (await RNFS.exists(cachedPath)) {
@@ -22,6 +22,7 @@ import GlobalEventEmitter from "../utils/GlobalEventEmitter"
22
22
  import {isGlassesConnected, useGlassesStore} from "../stores/glasses"
23
23
  import {resolveOtaManifestUrl} from "./otaManifestUrl"
24
24
  import {hotspotOtaTransport, type HotspotOtaPhase} from "./HotspotOtaTransport"
25
+ import type {OtaArtifactDownloadProgress} from "./OtaArtifactDownloader"
25
26
  import type {OtaCheckCurrentGlassesResult} from "./OtaUpdateCheckService"
26
27
  import {deriveDisplayState, type DisplayState} from "./otaDisplayState"
27
28
  import {
@@ -171,6 +172,7 @@ export interface OtaInstallSnapshot {
171
172
  /** Pre-ota_start phone staging/join state for a hotspot attempt. */
172
173
  hotspotPhase: HotspotOtaPhase
173
174
  hotspotArtifactPercent: number | null
175
+ hotspotArtifact: OtaArtifactDownloadProgress | null
174
176
  /** Transport selected from the checked glasses capabilities and Wi-Fi state. */
175
177
  transport: "wifi" | "hotspot"
176
178
  }
@@ -189,6 +191,7 @@ class OtaInstallCoordinator {
189
191
  private hotspotManifestUrl: string | null = null
190
192
  private hotspotPhase: HotspotOtaPhase = "idle"
191
193
  private hotspotArtifactPercent: number | null = null
194
+ private hotspotArtifact: OtaArtifactDownloadProgress | null = null
192
195
 
193
196
  // Genuinely session-local state (was component state/refs).
194
197
  private errorMsg = ""
@@ -303,6 +306,7 @@ class OtaInstallCoordinator {
303
306
  this.hotspotManifestUrl = null
304
307
  this.hotspotPhase = "idle"
305
308
  this.hotspotArtifactPercent = null
309
+ this.hotspotArtifact = null
306
310
  return this.selectedTransport
307
311
  }
308
312
 
@@ -510,6 +514,7 @@ class OtaInstallCoordinator {
510
514
  versionChangePhase: this.deriveVersionChangePhase(connected),
511
515
  hotspotPhase: this.hotspotPhase,
512
516
  hotspotArtifactPercent: this.hotspotArtifactPercent,
517
+ hotspotArtifact: this.hotspotArtifact ? {...this.hotspotArtifact} : null,
513
518
  transport: this.selectedTransport,
514
519
  }
515
520
  }
@@ -1501,7 +1506,9 @@ class OtaInstallCoordinator {
1501
1506
  }
1502
1507
  this.hotspotManifestUrl = await hotspotOtaTransport.prepare(this.preparedCheckResult, (progress) => {
1503
1508
  this.hotspotPhase = progress.phase
1504
- this.hotspotArtifactPercent = progress.artifact?.artifactPercent ?? null
1509
+ this.hotspotArtifact = progress.artifact ? {...progress.artifact} : null
1510
+ this.hotspotArtifactPercent =
1511
+ progress.artifact && progress.artifact.contentLength > 0 ? progress.artifact.artifactPercent : null
1505
1512
  this.emitInternalChange()
1506
1513
  })
1507
1514
  }
@@ -1581,6 +1588,7 @@ class OtaInstallCoordinator {
1581
1588
  this.hotspotManifestUrl = null
1582
1589
  this.hotspotPhase = "idle"
1583
1590
  this.hotspotArtifactPercent = null
1591
+ this.hotspotArtifact = null
1584
1592
  this.emitInternalChange()
1585
1593
  }
1586
1594