@mentra/engine 3.1.0-dev.28 → 3.1.0-dev.33
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/build/engine.d.ts +1 -0
- package/build/engine.d.ts.map +1 -1
- package/build/facades/ota.d.ts +5 -2
- package/build/facades/ota.d.ts.map +1 -1
- package/build/facades/ota.js +3 -0
- package/build/facades/ota.js.map +1 -1
- package/build/generated/releaseMetadata.js +5 -5
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js.map +1 -1
- package/build/react/MentraLiveOtaFlow.d.ts.map +1 -1
- package/build/react/MentraLiveOtaFlow.js +22 -1
- package/build/react/MentraLiveOtaFlow.js.map +1 -1
- package/build/react/useMentraLiveOta.d.ts +3 -0
- package/build/react/useMentraLiveOta.d.ts.map +1 -1
- package/build/react/useMentraLiveOta.js +40 -7
- package/build/react/useMentraLiveOta.js.map +1 -1
- package/build/services/OtaAutoChain.d.ts +7 -2
- package/build/services/OtaAutoChain.d.ts.map +1 -1
- package/build/services/OtaAutoChain.js +8 -2
- package/build/services/OtaAutoChain.js.map +1 -1
- package/package.json +6 -6
- package/src/facades/ota.ts +13 -1
- package/src/generated/releaseMetadata.ts +5 -5
- package/src/index.ts +1 -0
- package/src/react/MentraLiveOtaFlow.tsx +49 -9
- package/src/react/useMentraLiveOta.ts +57 -17
- package/src/services/OtaAutoChain.ts +22 -2
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
/* eslint-disable react-native/no-raw-text -- BodyText and PercentText are local Text wrappers. */
|
|
2
2
|
import React, {useMemo} from "react"
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
ActivityIndicator,
|
|
5
|
+
Pressable,
|
|
6
|
+
ScrollView,
|
|
7
|
+
StyleSheet,
|
|
8
|
+
Text,
|
|
9
|
+
View,
|
|
10
|
+
type StyleProp,
|
|
11
|
+
type ViewStyle,
|
|
12
|
+
} from "react-native"
|
|
4
13
|
import {SafeAreaView} from "react-native-safe-area-context"
|
|
5
14
|
import Svg, {Path, Rect} from "react-native-svg"
|
|
6
15
|
|
|
@@ -224,6 +233,7 @@ function OtaFlowContent({
|
|
|
224
233
|
icon="check"
|
|
225
234
|
title={translate("ota:upToDate")}>
|
|
226
235
|
<BodyText colors={colors}>{translate("ota:noUpdatesAvailable")}</BodyText>
|
|
236
|
+
<ChangelogList changelogs={state.changelogs} colors={colors} />
|
|
227
237
|
</FlowPage>
|
|
228
238
|
)
|
|
229
239
|
}
|
|
@@ -279,10 +289,10 @@ function OtaFlowContent({
|
|
|
279
289
|
state.hotspotPhase === "downloading"
|
|
280
290
|
? "Downloading update to phone..."
|
|
281
291
|
: state.hotspotPhase === "starting_hotspot"
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
292
|
+
? "Starting glasses hotspot..."
|
|
293
|
+
: state.hotspotPhase === "joining_hotspot"
|
|
294
|
+
? "Connecting phone to glasses..."
|
|
295
|
+
: "Starting update..."
|
|
286
296
|
return (
|
|
287
297
|
<FlowPage colors={colors} icon="download" title={title}>
|
|
288
298
|
{state.hotspotPhase === "downloading" && state.hotspotArtifactPercent !== null ? (
|
|
@@ -334,13 +344,13 @@ function OtaFlowContent({
|
|
|
334
344
|
const title = state.versionChangeConverged
|
|
335
345
|
? translate("ota:versionChangeComplete")
|
|
336
346
|
: state.versionChange
|
|
337
|
-
|
|
338
|
-
|
|
347
|
+
? translate("ota:versionChangeFirmwarePassComplete")
|
|
348
|
+
: "Update complete!"
|
|
339
349
|
const message = state.versionChangeConverged
|
|
340
350
|
? translate("ota:versionChangeCompleteMessage")
|
|
341
351
|
: state.versionChange
|
|
342
|
-
|
|
343
|
-
|
|
352
|
+
? translate("ota:versionChangeFirmwarePassCompleteMessage")
|
|
353
|
+
: "Your glasses are up to date."
|
|
344
354
|
return (
|
|
345
355
|
<FlowPage
|
|
346
356
|
actions={
|
|
@@ -354,6 +364,7 @@ function OtaFlowContent({
|
|
|
354
364
|
icon="check"
|
|
355
365
|
title={title}>
|
|
356
366
|
<BodyText colors={colors}>{message}</BodyText>
|
|
367
|
+
<ChangelogList changelogs={state.changelogs} colors={colors} />
|
|
357
368
|
</FlowPage>
|
|
358
369
|
)
|
|
359
370
|
}
|
|
@@ -426,6 +437,30 @@ function PercentText({colors, percent}: {colors: MentraLiveOtaFlowTheme; percent
|
|
|
426
437
|
return <Text style={[styles.percent, {color: colors.primary}]}>{Math.round(percent)}%</Text>
|
|
427
438
|
}
|
|
428
439
|
|
|
440
|
+
function ChangelogList({
|
|
441
|
+
changelogs,
|
|
442
|
+
colors,
|
|
443
|
+
}: {
|
|
444
|
+
changelogs: MentraLiveOtaController["state"]["changelogs"]
|
|
445
|
+
colors: MentraLiveOtaFlowTheme
|
|
446
|
+
}) {
|
|
447
|
+
if (changelogs.length === 0) return null
|
|
448
|
+
return (
|
|
449
|
+
<ScrollView contentContainerStyle={styles.changelogContent} style={styles.changelogList}>
|
|
450
|
+
{changelogs.map((entry) => (
|
|
451
|
+
<View key={entry.version} style={styles.changelogEntry}>
|
|
452
|
+
<Text selectable style={[styles.changelogVersion, {color: colors.foreground}]}>
|
|
453
|
+
{entry.version}
|
|
454
|
+
</Text>
|
|
455
|
+
<Text selectable style={[styles.changelogMarkdown, {color: colors.textDim}]}>
|
|
456
|
+
{entry.markdown}
|
|
457
|
+
</Text>
|
|
458
|
+
</View>
|
|
459
|
+
))}
|
|
460
|
+
</ScrollView>
|
|
461
|
+
)
|
|
462
|
+
}
|
|
463
|
+
|
|
429
464
|
function FlowButton({
|
|
430
465
|
colors,
|
|
431
466
|
disabled = false,
|
|
@@ -494,6 +529,11 @@ const styles = StyleSheet.create({
|
|
|
494
529
|
percent: {fontSize: 30, fontVariant: ["tabular-nums"], fontWeight: "700"},
|
|
495
530
|
progressTrack: {borderRadius: 4, height: 8, maxWidth: 420, overflow: "hidden", width: "100%"},
|
|
496
531
|
progressFill: {borderRadius: 4, height: 8},
|
|
532
|
+
changelogList: {flexShrink: 1, maxHeight: 260, maxWidth: 420, width: "100%"},
|
|
533
|
+
changelogContent: {gap: 18, paddingVertical: 4},
|
|
534
|
+
changelogEntry: {gap: 8},
|
|
535
|
+
changelogVersion: {fontSize: 16, fontWeight: "600"},
|
|
536
|
+
changelogMarkdown: {fontSize: 14, lineHeight: 20},
|
|
497
537
|
button: {
|
|
498
538
|
alignItems: "center",
|
|
499
539
|
borderRadius: 50,
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
clearOtaAutoChainReconnectWait,
|
|
7
7
|
isOtaAutoChainActive,
|
|
8
8
|
otaAutoChainFingerprint,
|
|
9
|
+
otaAutoChainReleaseRange,
|
|
9
10
|
otaAutoChainReconnectWaitRemaining,
|
|
10
11
|
stopOtaAutoChain,
|
|
11
12
|
tryAdvanceOtaAutoChain,
|
|
@@ -18,6 +19,7 @@ import {
|
|
|
18
19
|
} from "../services/OtaErrorMapping"
|
|
19
20
|
import type {OtaInstallSnapshot} from "../services/OtaInstallCoordinator"
|
|
20
21
|
import type {OtaCheckCurrentGlassesResult} from "../services/OtaUpdateCheckService"
|
|
22
|
+
import type {ReleaseChangelog} from "../facades/ota"
|
|
21
23
|
import {useEngineSnapshot} from "./useEngineSnapshot"
|
|
22
24
|
|
|
23
25
|
export type MentraLiveOtaFlowPage = "check" | "progress"
|
|
@@ -84,6 +86,8 @@ export type MentraLiveOtaState = {
|
|
|
84
86
|
canDiscard: boolean
|
|
85
87
|
canOpenWifiSetup: boolean
|
|
86
88
|
continueDisabled: boolean
|
|
89
|
+
/** Release notes crossed by this update, newest first. Populated on completion. */
|
|
90
|
+
changelogs: ReleaseChangelog[]
|
|
87
91
|
}
|
|
88
92
|
|
|
89
93
|
export type UseMentraLiveOtaOptions = {
|
|
@@ -115,16 +119,34 @@ type CheckState = "checking" | "update_available" | "no_update" | "dev_build" |
|
|
|
115
119
|
const AUTO_CHAIN_NETWORK_RETRY_DELAY_MS = 5000
|
|
116
120
|
const AUTO_CHAIN_COMPLETE_DELAY_MS = 750
|
|
117
121
|
|
|
122
|
+
function targetVersion(result: OtaCheckCurrentGlassesResult): string | null {
|
|
123
|
+
return result.updateInfo?.versionName ?? result.latestVersionInfo?.versionName ?? null
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function releaseChangelogsForActiveChain(): ReleaseChangelog[] {
|
|
127
|
+
const range = otaAutoChainReleaseRange()
|
|
128
|
+
if (!range?.toVersion) return []
|
|
129
|
+
try {
|
|
130
|
+
return ota.getReleaseChangelogs(range.fromVersion, range.toVersion)
|
|
131
|
+
} catch {
|
|
132
|
+
try {
|
|
133
|
+
return ota.getReleaseChangelogs(null, range.toVersion)
|
|
134
|
+
} catch {
|
|
135
|
+
return []
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
118
140
|
function installProgress(snapshot: OtaInstallSnapshot): number | null {
|
|
119
141
|
if (snapshot.displayState !== "updating") return null
|
|
120
142
|
const {otaStatus} = snapshot
|
|
121
143
|
const isDownload = otaStatus?.phase === "download"
|
|
122
144
|
const totalSteps = otaStatus?.totalSteps ?? 1
|
|
123
145
|
const rawPercent = isDownload
|
|
124
|
-
?
|
|
146
|
+
? otaStatus?.stepPercent ?? 0
|
|
125
147
|
: totalSteps >= 2
|
|
126
|
-
|
|
127
|
-
|
|
148
|
+
? otaStatus?.overallPercent ?? 0
|
|
149
|
+
: otaStatus?.stepPercent ?? 0
|
|
128
150
|
return Math.min(Math.max(rawPercent, snapshot.mtkInstallStallSimulatedPercent ?? 0, 0), 100)
|
|
129
151
|
}
|
|
130
152
|
|
|
@@ -169,6 +191,7 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
|
|
|
169
191
|
const [isVersionChange, setIsVersionChange] = useState(false)
|
|
170
192
|
const [errorKind, setErrorKind] = useState<"network" | "pin_unavailable">("network")
|
|
171
193
|
const [updateFingerprint, setUpdateFingerprint] = useState<string | null>(null)
|
|
194
|
+
const [completedChangelogs, setCompletedChangelogs] = useState<ReleaseChangelog[]>([])
|
|
172
195
|
const [checkGeneration, setCheckGeneration] = useState(0)
|
|
173
196
|
const performCheckGenerationRef = useRef(0)
|
|
174
197
|
const activeCheckKeyRef = useRef<string | null>(null)
|
|
@@ -319,7 +342,11 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
|
|
|
319
342
|
if (!snapshot.wifiConnected && snapshot.hotspotOtaVersion !== 1) {
|
|
320
343
|
stopOtaAutoChain()
|
|
321
344
|
} else {
|
|
322
|
-
const admission = tryAdvanceOtaAutoChain(
|
|
345
|
+
const admission = tryAdvanceOtaAutoChain(
|
|
346
|
+
fingerprint,
|
|
347
|
+
result.updateInfo.isDowngrade === true,
|
|
348
|
+
targetVersion(result),
|
|
349
|
+
)
|
|
323
350
|
if (admission.advance) {
|
|
324
351
|
checkCompletedRef.current = true
|
|
325
352
|
ota.installSession.prepare(result)
|
|
@@ -332,6 +359,7 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
|
|
|
332
359
|
setCheckState("update_available")
|
|
333
360
|
return
|
|
334
361
|
}
|
|
362
|
+
if (isOtaAutoChainActive()) setCompletedChangelogs(releaseChangelogsForActiveChain())
|
|
335
363
|
stopOtaAutoChain()
|
|
336
364
|
checkCompletedRef.current = true
|
|
337
365
|
ota.clearUpdateAvailable()
|
|
@@ -397,6 +425,7 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
|
|
|
397
425
|
}, [installSnapshot.connected, installSnapshot.displayState, page, returnToCheck])
|
|
398
426
|
|
|
399
427
|
const check = useCallback(() => {
|
|
428
|
+
setCompletedChangelogs([])
|
|
400
429
|
setPage("check")
|
|
401
430
|
setCheckState("checking")
|
|
402
431
|
setCheckGeneration((generation) => generation + 1)
|
|
@@ -420,7 +449,13 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
|
|
|
420
449
|
return
|
|
421
450
|
}
|
|
422
451
|
ota.installSession.prepare(result)
|
|
423
|
-
|
|
452
|
+
setCompletedChangelogs([])
|
|
453
|
+
if (updateFingerprint) {
|
|
454
|
+
beginOtaAutoChain(updateFingerprint, isVersionChange, {
|
|
455
|
+
fromVersion: snapshot.appVersion,
|
|
456
|
+
toVersion: targetVersion(result),
|
|
457
|
+
})
|
|
458
|
+
}
|
|
424
459
|
navigateToProgress()
|
|
425
460
|
}, [check, isVersionChange, navigateToProgress, page, updateFingerprint])
|
|
426
461
|
|
|
@@ -490,6 +525,7 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
|
|
|
490
525
|
canDiscard: false,
|
|
491
526
|
canOpenWifiSetup: false,
|
|
492
527
|
continueDisabled: false,
|
|
528
|
+
changelogs: [],
|
|
493
529
|
}
|
|
494
530
|
}
|
|
495
531
|
|
|
@@ -499,22 +535,22 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
|
|
|
499
535
|
checkState === "checking"
|
|
500
536
|
? "checking"
|
|
501
537
|
: checkState === "update_available"
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
538
|
+
? wifiRequired
|
|
539
|
+
? "wifi_required"
|
|
540
|
+
: "update_available"
|
|
541
|
+
: checkState === "no_update"
|
|
542
|
+
? "up_to_date"
|
|
543
|
+
: checkState === "dev_build"
|
|
544
|
+
? "dev_build"
|
|
545
|
+
: errorKind === "pin_unavailable"
|
|
546
|
+
? "update_info_unavailable"
|
|
547
|
+
: "check_failed"
|
|
512
548
|
const error: MentraLiveOtaError | null =
|
|
513
549
|
screen === "check_failed"
|
|
514
550
|
? {code: "check_failed", message: "Couldn't check for updates. Please check your connection and try again."}
|
|
515
551
|
: screen === "update_info_unavailable"
|
|
516
|
-
|
|
517
|
-
|
|
552
|
+
? {code: "update_info_unavailable", message: "Update information for this app version is unavailable."}
|
|
553
|
+
: null
|
|
518
554
|
return {
|
|
519
555
|
screen,
|
|
520
556
|
connected: otaSnapshot.connected,
|
|
@@ -543,6 +579,7 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
|
|
|
543
579
|
canDiscard: false,
|
|
544
580
|
canOpenWifiSetup: screen === "wifi_required",
|
|
545
581
|
continueDisabled: false,
|
|
582
|
+
changelogs: screen === "up_to_date" ? completedChangelogs : [],
|
|
546
583
|
}
|
|
547
584
|
}
|
|
548
585
|
|
|
@@ -568,6 +605,7 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
|
|
|
568
605
|
}
|
|
569
606
|
: null
|
|
570
607
|
const totalSteps = installSnapshot.otaStatus?.totalSteps ?? null
|
|
608
|
+
const changelogs = screen === "complete" ? releaseChangelogsForActiveChain() : []
|
|
571
609
|
return {
|
|
572
610
|
screen,
|
|
573
611
|
connected: installSnapshot.connected,
|
|
@@ -599,9 +637,11 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
|
|
|
599
637
|
canDiscard: screen === "disconnected",
|
|
600
638
|
canOpenWifiSetup: screen === "failed" && showChangeWifi,
|
|
601
639
|
continueDisabled: installSnapshot.continueButtonDisabled,
|
|
640
|
+
changelogs,
|
|
602
641
|
}
|
|
603
642
|
}, [
|
|
604
643
|
checkState,
|
|
644
|
+
completedChangelogs,
|
|
605
645
|
errorKind,
|
|
606
646
|
firmwareRestarting,
|
|
607
647
|
installSnapshot,
|
|
@@ -6,10 +6,16 @@ export const OTA_AUTO_CHAIN_RECONNECT_TIMEOUT_MS = 120_000
|
|
|
6
6
|
type OtaAutoChainSession = {
|
|
7
7
|
approvedDowngrade: boolean
|
|
8
8
|
passCount: number
|
|
9
|
+
releaseRange: OtaAutoChainReleaseRange
|
|
9
10
|
reconnectDeadline: number | null
|
|
10
11
|
seenFingerprints: Set<string>
|
|
11
12
|
}
|
|
12
13
|
|
|
14
|
+
export type OtaAutoChainReleaseRange = {
|
|
15
|
+
fromVersion: string | null
|
|
16
|
+
toVersion: string | null
|
|
17
|
+
}
|
|
18
|
+
|
|
13
19
|
export type OtaAutoChainAdvanceResult =
|
|
14
20
|
| {advance: true; passCount: number}
|
|
15
21
|
| {advance: false; reason: "inactive" | "duplicate" | "max_passes" | "downgrade_not_approved"}
|
|
@@ -42,10 +48,15 @@ export function otaAutoChainFingerprint(result: OtaCheckCurrentGlassesResult): s
|
|
|
42
48
|
}
|
|
43
49
|
|
|
44
50
|
/** Start a chain after the user explicitly approves its first update pass. */
|
|
45
|
-
export function beginOtaAutoChain(
|
|
51
|
+
export function beginOtaAutoChain(
|
|
52
|
+
initialFingerprint: string,
|
|
53
|
+
approvedDowngrade: boolean,
|
|
54
|
+
releaseRange: OtaAutoChainReleaseRange,
|
|
55
|
+
): void {
|
|
46
56
|
session = {
|
|
47
57
|
approvedDowngrade,
|
|
48
58
|
passCount: 1,
|
|
59
|
+
releaseRange: {...releaseRange},
|
|
49
60
|
reconnectDeadline: null,
|
|
50
61
|
seenFingerprints: new Set([initialFingerprint]),
|
|
51
62
|
}
|
|
@@ -55,6 +66,10 @@ export function isOtaAutoChainActive(): boolean {
|
|
|
55
66
|
return session !== null
|
|
56
67
|
}
|
|
57
68
|
|
|
69
|
+
export function otaAutoChainReleaseRange(): OtaAutoChainReleaseRange | null {
|
|
70
|
+
return session ? {...session.releaseRange} : null
|
|
71
|
+
}
|
|
72
|
+
|
|
58
73
|
/** End the current chain. Safe to call when no chain is active. */
|
|
59
74
|
export function stopOtaAutoChain(): void {
|
|
60
75
|
session = null
|
|
@@ -80,7 +95,11 @@ export function clearOtaAutoChainReconnectWait(): void {
|
|
|
80
95
|
* Admit the next pass of an active chain. Repeated offers, unexpectedly
|
|
81
96
|
* destructive downgrades, and runaway chains fail closed and end automation.
|
|
82
97
|
*/
|
|
83
|
-
export function tryAdvanceOtaAutoChain(
|
|
98
|
+
export function tryAdvanceOtaAutoChain(
|
|
99
|
+
fingerprint: string,
|
|
100
|
+
isDowngrade: boolean,
|
|
101
|
+
targetVersion: string | null,
|
|
102
|
+
): OtaAutoChainAdvanceResult {
|
|
84
103
|
if (!session) {
|
|
85
104
|
return {advance: false, reason: "inactive"}
|
|
86
105
|
}
|
|
@@ -102,5 +121,6 @@ export function tryAdvanceOtaAutoChain(fingerprint: string, isDowngrade: boolean
|
|
|
102
121
|
|
|
103
122
|
session.seenFingerprints.add(fingerprint)
|
|
104
123
|
session.passCount += 1
|
|
124
|
+
if (targetVersion) session.releaseRange.toVersion = targetVersion
|
|
105
125
|
return {advance: true, passCount: session.passCount}
|
|
106
126
|
}
|