@mentra/engine 3.2.0-dev.217 → 3.2.0-dev.222
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/generated/releaseMetadata.js +5 -5
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/index.d.ts +3 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2 -1
- package/build/index.js.map +1 -1
- package/build/react/MentraLiveOtaFlow.d.ts.map +1 -1
- package/build/react/MentraLiveOtaFlow.js +19 -3
- package/build/react/MentraLiveOtaFlow.js.map +1 -1
- package/build/react/useMentraLiveOta.d.ts +12 -0
- package/build/react/useMentraLiveOta.d.ts.map +1 -1
- package/build/react/useMentraLiveOta.js +31 -5
- package/build/react/useMentraLiveOta.js.map +1 -1
- package/build/services/AcsMeetingService.d.ts +360 -10
- package/build/services/AcsMeetingService.d.ts.map +1 -1
- package/build/services/AcsMeetingService.js +836 -19
- package/build/services/AcsMeetingService.js.map +1 -1
- package/build/services/AudioPlaybackService.d.ts +6 -0
- package/build/services/AudioPlaybackService.d.ts.map +1 -1
- package/build/services/AudioPlaybackService.js +4 -3
- package/build/services/AudioPlaybackService.js.map +1 -1
- package/build/services/GlassesMicProbe.d.ts +78 -0
- package/build/services/GlassesMicProbe.d.ts.map +1 -0
- package/build/services/GlassesMicProbe.js +258 -0
- package/build/services/GlassesMicProbe.js.map +1 -0
- package/build/services/LocalMiniappRuntime.d.ts +83 -0
- package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
- package/build/services/LocalMiniappRuntime.js +714 -39
- package/build/services/LocalMiniappRuntime.js.map +1 -1
- package/build/services/MentraJSLogPipeline.d.ts +39 -0
- package/build/services/MentraJSLogPipeline.d.ts.map +1 -1
- package/build/services/MentraJSLogPipeline.js +59 -3
- package/build/services/MentraJSLogPipeline.js.map +1 -1
- package/build/services/MentraJSRouter.d.ts +1 -1
- package/build/services/MentraJSRouter.d.ts.map +1 -1
- package/build/services/MentraJSRouter.js +2 -2
- package/build/services/MentraJSRouter.js.map +1 -1
- package/build/services/MicStateCoordinator.d.ts +22 -0
- package/build/services/MicStateCoordinator.d.ts.map +1 -1
- package/build/services/MicStateCoordinator.js +34 -3
- package/build/services/MicStateCoordinator.js.map +1 -1
- package/build/services/OtaErrorMapping.d.ts +25 -2
- package/build/services/OtaErrorMapping.d.ts.map +1 -1
- package/build/services/OtaErrorMapping.js +68 -23
- package/build/services/OtaErrorMapping.js.map +1 -1
- package/build/services/OtaInstallCoordinator.d.ts.map +1 -1
- package/build/services/OtaInstallCoordinator.js +6 -1
- package/build/services/OtaInstallCoordinator.js.map +1 -1
- package/build/services/PhoneStreamCoordinator.d.ts +8 -0
- package/build/services/PhoneStreamCoordinator.d.ts.map +1 -1
- package/build/services/PhoneStreamCoordinator.js +2 -0
- package/build/services/PhoneStreamCoordinator.js.map +1 -1
- package/build/services/SoftapCallTransport.d.ts +365 -0
- package/build/services/SoftapCallTransport.d.ts.map +1 -0
- package/build/services/SoftapCallTransport.js +722 -0
- package/build/services/SoftapCallTransport.js.map +1 -0
- package/build/services/SoftapCleanupBarrier.d.ts +45 -0
- package/build/services/SoftapCleanupBarrier.d.ts.map +1 -0
- package/build/services/SoftapCleanupBarrier.js +51 -0
- package/build/services/SoftapCleanupBarrier.js.map +1 -0
- package/build/utils/pcm16.d.ts +35 -0
- package/build/utils/pcm16.d.ts.map +1 -0
- package/build/utils/pcm16.js +85 -0
- package/build/utils/pcm16.js.map +1 -0
- package/build/utils/softapTrace.d.ts +39 -0
- package/build/utils/softapTrace.d.ts.map +1 -0
- package/build/utils/softapTrace.js +124 -0
- package/build/utils/softapTrace.js.map +1 -0
- package/package.json +7 -7
- package/src/generated/releaseMetadata.ts +5 -5
- package/src/index.ts +8 -0
- package/src/react/MentraLiveOtaFlow.tsx +22 -3
- package/src/react/useMentraLiveOta.ts +44 -6
- package/src/services/AcsMeetingService.ts +956 -22
- package/src/services/AudioPlaybackService.ts +12 -3
- package/src/services/GlassesMicProbe.ts +300 -0
- package/src/services/LocalMiniappRuntime.ts +775 -42
- package/src/services/MentraJSLogPipeline.ts +70 -3
- package/src/services/MentraJSRouter.ts +2 -2
- package/src/services/MicStateCoordinator.ts +35 -3
- package/src/services/OtaErrorMapping.ts +77 -25
- package/src/services/OtaInstallCoordinator.ts +6 -1
- package/src/services/PhoneStreamCoordinator.ts +10 -0
- package/src/services/SoftapCallTransport.ts +928 -0
- package/src/services/SoftapCleanupBarrier.ts +72 -0
- package/src/utils/pcm16.ts +93 -0
- package/src/utils/softapTrace.ts +133 -0
|
@@ -73,6 +73,34 @@ export interface ThrottleOptions {
|
|
|
73
73
|
now?: () => number
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
/** A per-package override of the shared budget. Both fields are absolute, not multipliers. */
|
|
77
|
+
export interface PackageLogBudget {
|
|
78
|
+
tokensPerSecond: number
|
|
79
|
+
bucketCapacity: number
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Budgets for miniapps under active diagnosis.
|
|
84
|
+
*
|
|
85
|
+
* The default ceiling protects the host from a miniapp that logs in a render loop, and it is the
|
|
86
|
+
* right default. It is the wrong ceiling for a miniapp someone is actively debugging: a single
|
|
87
|
+
* instrumented call join emits several hundred lines in its first few seconds, which exhausts the
|
|
88
|
+
* burst and then drops everything that follows — and the drops land exactly where the interesting
|
|
89
|
+
* part is. A log with silent holes in the failure window is worse than no log, because it is read
|
|
90
|
+
* as complete.
|
|
91
|
+
*
|
|
92
|
+
* So the ceiling is raised for named packages rather than lowered for everyone. Anything not
|
|
93
|
+
* listed here keeps the protective default.
|
|
94
|
+
*/
|
|
95
|
+
export const DIAGNOSTIC_LOG_BUDGETS: Readonly<Record<string, PackageLogBudget>> = {
|
|
96
|
+
// Mentra Call is instrumented end to end — UI, background, and the host call path — so the
|
|
97
|
+
// whole of a join, a cancel, and a teardown can be read back from one capture.
|
|
98
|
+
"com.mentra.call": {tokensPerSecond: 200, bucketCapacity: 5_000},
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Ring-buffer window for a package in {@link DIAGNOSTIC_LOG_BUDGETS}. See `capacityFor`. */
|
|
102
|
+
export const DIAGNOSTIC_RING_CAPACITY = 2_000
|
|
103
|
+
|
|
76
104
|
interface PackageBucket {
|
|
77
105
|
tokens: number
|
|
78
106
|
lastRefillAtMs: number
|
|
@@ -85,6 +113,7 @@ export class MentraJSLogThrottle {
|
|
|
85
113
|
private readonly bucketCapacity: number
|
|
86
114
|
private readonly now: () => number
|
|
87
115
|
private readonly buckets: Map<string, PackageBucket> = new Map()
|
|
116
|
+
private readonly budgets: Map<string, PackageLogBudget> = new Map(Object.entries(DIAGNOSTIC_LOG_BUDGETS))
|
|
88
117
|
|
|
89
118
|
constructor(opts: ThrottleOptions = {}) {
|
|
90
119
|
this.tokensPerSecond = opts.tokensPerSecond ?? 100 / 60
|
|
@@ -92,6 +121,29 @@ export class MentraJSLogThrottle {
|
|
|
92
121
|
this.now = opts.now ?? (() => Date.now())
|
|
93
122
|
}
|
|
94
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Raise (or lower) one package's budget at runtime.
|
|
126
|
+
*
|
|
127
|
+
* Exists so a support session can widen the pipe for the miniapp being investigated without a
|
|
128
|
+
* rebuild, and so tests can exercise a budget without depending on the shipped table.
|
|
129
|
+
*/
|
|
130
|
+
setPackageBudget(packageName: string, budget: PackageLogBudget | null): void {
|
|
131
|
+
if (budget) this.budgets.set(packageName, budget)
|
|
132
|
+
else this.budgets.delete(packageName)
|
|
133
|
+
// The bucket holds a capacity snapshot, so a stale one would keep enforcing the old ceiling
|
|
134
|
+
// until it happened to refill. Dropping it re-derives from the new budget on the next line.
|
|
135
|
+
this.buckets.delete(packageName)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private budgetFor(packageName: string): PackageLogBudget {
|
|
139
|
+
return (
|
|
140
|
+
this.budgets.get(packageName) ?? {
|
|
141
|
+
tokensPerSecond: this.tokensPerSecond,
|
|
142
|
+
bucketCapacity: this.bucketCapacity,
|
|
143
|
+
}
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
|
|
95
147
|
/**
|
|
96
148
|
* Try to consume one token for the named package. Returns either:
|
|
97
149
|
* - `{allowed: true}` — caller emits the original log line.
|
|
@@ -102,17 +154,18 @@ export class MentraJSLogThrottle {
|
|
|
102
154
|
*/
|
|
103
155
|
consume(packageName: string): {allowed: true} | {allowed: false; throttledLine?: string} {
|
|
104
156
|
const at = this.now()
|
|
157
|
+
const budget = this.budgetFor(packageName)
|
|
105
158
|
let bucket = this.buckets.get(packageName)
|
|
106
159
|
if (!bucket) {
|
|
107
160
|
bucket = {
|
|
108
|
-
tokens:
|
|
161
|
+
tokens: budget.bucketCapacity,
|
|
109
162
|
lastRefillAtMs: at,
|
|
110
163
|
pendingDrops: 0,
|
|
111
164
|
}
|
|
112
165
|
this.buckets.set(packageName, bucket)
|
|
113
166
|
} else {
|
|
114
167
|
const elapsedSec = (at - bucket.lastRefillAtMs) / 1000
|
|
115
|
-
bucket.tokens = Math.min(
|
|
168
|
+
bucket.tokens = Math.min(budget.bucketCapacity, bucket.tokens + elapsedSec * budget.tokensPerSecond)
|
|
116
169
|
bucket.lastRefillAtMs = at
|
|
117
170
|
}
|
|
118
171
|
if (bucket.tokens >= 1) {
|
|
@@ -146,6 +199,19 @@ export class MentraJSLogRingBuffer {
|
|
|
146
199
|
this.capacity = capacityPerPackage
|
|
147
200
|
}
|
|
148
201
|
|
|
202
|
+
/**
|
|
203
|
+
* Capacity for a package under diagnosis.
|
|
204
|
+
*
|
|
205
|
+
* This buffer is the miniapp's last words in a crash report, and 200 lines of a heavily
|
|
206
|
+
* instrumented miniapp is a couple of seconds — so the report would arrive holding the
|
|
207
|
+
* aftermath and none of the cause. A package with a raised log budget needs a window long
|
|
208
|
+
* enough to contain whatever produced the crash.
|
|
209
|
+
*/
|
|
210
|
+
private capacityFor(packageName: string): number {
|
|
211
|
+
const budget = DIAGNOSTIC_LOG_BUDGETS[packageName]
|
|
212
|
+
return budget ? Math.max(this.capacity, DIAGNOSTIC_RING_CAPACITY) : this.capacity
|
|
213
|
+
}
|
|
214
|
+
|
|
149
215
|
push(packageName: string, line: string): void {
|
|
150
216
|
let buf = this.buffers.get(packageName)
|
|
151
217
|
if (!buf) {
|
|
@@ -153,7 +219,8 @@ export class MentraJSLogRingBuffer {
|
|
|
153
219
|
this.buffers.set(packageName, buf)
|
|
154
220
|
}
|
|
155
221
|
buf.push(line)
|
|
156
|
-
|
|
222
|
+
const capacity = this.capacityFor(packageName)
|
|
223
|
+
while (buf.length > capacity) buf.shift()
|
|
157
224
|
}
|
|
158
225
|
|
|
159
226
|
snapshot(packageName: string): string[] {
|
|
@@ -379,9 +379,9 @@ export class MentraJSRouter {
|
|
|
379
379
|
* router gates this to registered packages and keeps LocalMiniappView from
|
|
380
380
|
* reaching into runtime internals.
|
|
381
381
|
*/
|
|
382
|
-
probeForegroundLiveness(packageName: string, reason = "foreground-open"): void {
|
|
382
|
+
probeForegroundLiveness(packageName: string, reason = "foreground-open", timeoutMs?: number): void {
|
|
383
383
|
if (!this.registered.has(packageName)) return
|
|
384
|
-
this.runtime.probeForegroundLiveness(packageName, reason)
|
|
384
|
+
this.runtime.probeForegroundLiveness(packageName, reason, timeoutMs)
|
|
385
385
|
}
|
|
386
386
|
|
|
387
387
|
// ----------------------------------------------------------------
|
|
@@ -48,6 +48,14 @@ class MicStateCoordinator {
|
|
|
48
48
|
// Local miniapp requirements (set when miniapps subscribe to audio streams)
|
|
49
49
|
private localWantsPcm = false
|
|
50
50
|
private localWantsLc3 = false
|
|
51
|
+
/**
|
|
52
|
+
* A live ACS call taking the wearer's voice off the glasses over BLE LC3.
|
|
53
|
+
*
|
|
54
|
+
* Tracked separately from the miniapp requirement because the two have independent lifetimes:
|
|
55
|
+
* the call miniapp does not subscribe to `audio_chunk`, and a captions miniapp that stops mid-call
|
|
56
|
+
* must not take the call's microphone with it.
|
|
57
|
+
*/
|
|
58
|
+
private callWantsPcm = false
|
|
51
59
|
private configuredVad: boolean | undefined
|
|
52
60
|
private configuredLoudnessGate: boolean | undefined
|
|
53
61
|
private readonly miniappVadOverrides = new Map<string, GateOverride>()
|
|
@@ -75,6 +83,29 @@ class MicStateCoordinator {
|
|
|
75
83
|
this.applyUnion()
|
|
76
84
|
}
|
|
77
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Claim or release raw PCM on behalf of an active call.
|
|
88
|
+
*
|
|
89
|
+
* Called by AcsMeetingService around a call whose uplink is the glasses microphone over BLE LC3.
|
|
90
|
+
* Releasing is a claim release, not a mic shutdown: if a captions miniapp still wants PCM the
|
|
91
|
+
* microphone stays on, which is the whole reason this is a separate flag rather than a setter on
|
|
92
|
+
* the local requirement.
|
|
93
|
+
*/
|
|
94
|
+
public setCallRequirement(pcm: boolean): void {
|
|
95
|
+
if (this.callWantsPcm === pcm) return
|
|
96
|
+
this.callWantsPcm = pcm
|
|
97
|
+
console.log(`${LOG_TAG}: call requirement updated — pcm=${pcm}`)
|
|
98
|
+
this.applyUnion()
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Whether anything on this device needs a continuous raw-PCM timeline. Also the condition that
|
|
103
|
+
* forces hardware VAD off: a gate that drops silence turns a call into clipped half-words.
|
|
104
|
+
*/
|
|
105
|
+
private get wantsRawPcm(): boolean {
|
|
106
|
+
return this.localWantsPcm || this.callWantsPcm
|
|
107
|
+
}
|
|
108
|
+
|
|
78
109
|
/**
|
|
79
110
|
* Apply a miniapp-owned gate override without changing the OS preference.
|
|
80
111
|
* Overrides are lifecycle-scoped and last-live-owner-wins independently for
|
|
@@ -159,7 +190,7 @@ class MicStateCoordinator {
|
|
|
159
190
|
const vadOverride = this.latestOverride(this.miniappVadOverrides)
|
|
160
191
|
const loudnessOverride = this.latestOverride(this.miniappLoudnessGateOverrides)
|
|
161
192
|
|
|
162
|
-
if (this.
|
|
193
|
+
if (this.wantsRawPcm) {
|
|
163
194
|
runtimeSettings.voice_activity_detection_enabled = false
|
|
164
195
|
} else if (vadOverride) {
|
|
165
196
|
runtimeSettings.voice_activity_detection_enabled = vadOverride.enabled
|
|
@@ -176,7 +207,7 @@ class MicStateCoordinator {
|
|
|
176
207
|
* on-device PCM consumers; cloud audio uses LC3 through AudioCloudUplink.
|
|
177
208
|
*/
|
|
178
209
|
private applyUnion(): void {
|
|
179
|
-
const shouldSendPcm = this.
|
|
210
|
+
const shouldSendPcm = this.wantsRawPcm
|
|
180
211
|
const shouldSendLc3 = this.localWantsLc3
|
|
181
212
|
|
|
182
213
|
// console.log(
|
|
@@ -225,7 +256,7 @@ class MicStateCoordinator {
|
|
|
225
256
|
|
|
226
257
|
// Hardware VAD suppresses silence. Raw-audio consumers need a continuous
|
|
227
258
|
// timeline, so their requirement wins over both OS and miniapp VAD values.
|
|
228
|
-
if (this.
|
|
259
|
+
if (this.wantsRawPcm) patch.voice_activity_detection_enabled = false
|
|
229
260
|
else if (vadOverride) patch.voice_activity_detection_enabled = vadOverride.enabled
|
|
230
261
|
else if (this.configuredVad !== undefined) patch.voice_activity_detection_enabled = this.configuredVad
|
|
231
262
|
|
|
@@ -243,6 +274,7 @@ class MicStateCoordinator {
|
|
|
243
274
|
public reset(): void {
|
|
244
275
|
this.localWantsPcm = false
|
|
245
276
|
this.localWantsLc3 = false
|
|
277
|
+
this.callWantsPcm = false
|
|
246
278
|
this.applyUnion()
|
|
247
279
|
}
|
|
248
280
|
|
|
@@ -1,7 +1,79 @@
|
|
|
1
1
|
import type {OtaProgress, OtaStatus} from "../facades/ota"
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Copy keys for OTA failures the glasses report by code (`ota_status.error`), plus the
|
|
5
|
+
* phone-side BES restart instruction. Hosts translate these keys; the engine's own
|
|
6
|
+
* English copy for each key lives in `OTA_ERROR_ENGLISH_COPY`.
|
|
7
|
+
*
|
|
8
|
+
* The glasses-side producers are `sendProgressToPhone(..., "FAILED", errorCode)` in the
|
|
9
|
+
* ASG client's OtaHelper (download classification, install failures, and the downgrade
|
|
10
|
+
* handoff to the recovery worker). Keep this table in sync with those codes; anything
|
|
11
|
+
* else falls back to `OTA_ERROR_UNKNOWN_GLASSES_COPY_KEY` and the raw code is shown in a
|
|
12
|
+
* secondary line so support can still identify it.
|
|
13
|
+
*/
|
|
14
|
+
export const OTA_GLASSES_ERROR_COPY_KEYS: Readonly<Record<string, string>> = {
|
|
15
|
+
no_internet: "ota:errorNoInternet",
|
|
16
|
+
clock_skew: "ota:errorClockSkew",
|
|
17
|
+
ssl_error: "ota:errorSslError",
|
|
18
|
+
download_failed: "ota:errorDownloadFailed",
|
|
19
|
+
insufficient_storage: "ota:errorInsufficientStorage",
|
|
20
|
+
firmware_too_large: "ota:errorFirmwareTooLarge",
|
|
21
|
+
firmware_verify_failed: "ota:errorFirmwareVerifyFailed",
|
|
22
|
+
apk_verify_failed: "ota:errorApkVerifyFailed",
|
|
23
|
+
install_failed: "ota:errorInstallFailed",
|
|
24
|
+
apk_restart_guard_not_persisted: "ota:errorApkRestartGuardNotPersisted",
|
|
25
|
+
downgrade_handoff_refused: "ota:errorDowngradeHandoffRefused",
|
|
26
|
+
downgrade_handoff_failed: "ota:errorDowngradeHandoffFailed",
|
|
27
|
+
downgrade_transaction_stalled: "ota:errorDowngradeTransactionStalled",
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const OTA_ERROR_UNKNOWN_GLASSES_COPY_KEY = "ota:errorGlassesUnknown"
|
|
31
|
+
export const OTA_ERROR_GENERIC_COPY_KEY = "ota:errorGeneric"
|
|
32
|
+
export const OTA_ERROR_BES_RESTART_REQUIRED_COPY_KEY = "ota:errorBesRestartRequired"
|
|
33
|
+
|
|
34
|
+
/** English copy for every OTA error key above. Mirrored in the Mentra App's `ota` i18n namespace. */
|
|
35
|
+
export const OTA_ERROR_ENGLISH_COPY: Readonly<Record<string, string>> = {
|
|
36
|
+
"ota:errorNoInternet": "Glasses WiFi has no internet connection",
|
|
37
|
+
"ota:errorClockSkew": "Glasses clock is wrong — syncing time from your phone, then retrying update check",
|
|
38
|
+
"ota:errorSslError": "Secure connection failed — try a different WiFi network",
|
|
39
|
+
"ota:errorDownloadFailed": "Download failed — check glasses WiFi connection",
|
|
40
|
+
"ota:errorInsufficientStorage": "Not enough storage on your glasses — free up space before trying again",
|
|
41
|
+
"ota:errorFirmwareTooLarge": "Firmware file is unexpectedly large — please contact support",
|
|
42
|
+
"ota:errorFirmwareVerifyFailed": "Firmware verification failed — please try again or contact support",
|
|
43
|
+
"ota:errorApkVerifyFailed": "Update verification failed — please try again or contact support",
|
|
44
|
+
"ota:errorInstallFailed": "Install failed — please try again",
|
|
45
|
+
"ota:errorApkRestartGuardNotPersisted":
|
|
46
|
+
"Your glasses could not save the update before restarting. Restart your glasses and try again.",
|
|
47
|
+
"ota:errorDowngradeHandoffRefused":
|
|
48
|
+
"Your glasses could not start the version change. Restart your glasses and try again.",
|
|
49
|
+
"ota:errorDowngradeHandoffFailed":
|
|
50
|
+
"The recovery service on your glasses did not respond. Restart your glasses and try again.",
|
|
51
|
+
"ota:errorDowngradeTransactionStalled":
|
|
52
|
+
"The version change on your glasses did not finish. Restart your glasses and try again.",
|
|
53
|
+
"ota:errorGlassesUnknown": "Your glasses reported an unexpected error. Restart your glasses and try again.",
|
|
54
|
+
"ota:errorGeneric": "Update failed",
|
|
55
|
+
"ota:errorBesRestartRequired": "Restart your glasses to safely exit firmware update mode before trying again",
|
|
56
|
+
"ota:errorCode": "Error code: {{code}}",
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const BES_INSTALL_RESTART_MESSAGE = OTA_ERROR_ENGLISH_COPY[OTA_ERROR_BES_RESTART_REQUIRED_COPY_KEY]
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Copy key for a glasses-reported failure code. Unknown codes get the generic
|
|
63
|
+
* glasses-error copy; a missing code gets the plain generic copy.
|
|
64
|
+
*/
|
|
65
|
+
export function otaErrorCopyKey(error?: string | null): string {
|
|
66
|
+
if (!error) return OTA_ERROR_GENERIC_COPY_KEY
|
|
67
|
+
// Own-property lookup: a code that happens to name an inherited Object member
|
|
68
|
+
// ("constructor", "toString", ...) is unknown, not a mapped key.
|
|
69
|
+
return hasOwn(OTA_GLASSES_ERROR_COPY_KEYS, error)
|
|
70
|
+
? OTA_GLASSES_ERROR_COPY_KEYS[error]
|
|
71
|
+
: OTA_ERROR_UNKNOWN_GLASSES_COPY_KEY
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function hasOwn(table: Readonly<Record<string, string>>, key: string): boolean {
|
|
75
|
+
return Object.prototype.hasOwnProperty.call(table, key)
|
|
76
|
+
}
|
|
5
77
|
|
|
6
78
|
function isDownloadPhaseSnapshot(
|
|
7
79
|
otaStatus: OtaStatus | null | undefined,
|
|
@@ -43,29 +115,9 @@ export function shouldShowChangeWifiForOtaDownloadFailure(
|
|
|
43
115
|
return false
|
|
44
116
|
}
|
|
45
117
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return "Glasses WiFi has no internet connection"
|
|
50
|
-
case "clock_skew":
|
|
51
|
-
return "Glasses clock is wrong — syncing time from your phone, then retrying update check"
|
|
52
|
-
case "ssl_error":
|
|
53
|
-
return "Secure connection failed — try a different WiFi network"
|
|
54
|
-
case "download_failed":
|
|
55
|
-
return "Download failed — check glasses WiFi connection"
|
|
56
|
-
case "insufficient_storage":
|
|
57
|
-
return "Not enough storage on your glasses — free up space before trying again"
|
|
58
|
-
case "firmware_too_large":
|
|
59
|
-
return "Firmware file is unexpectedly large — please contact support"
|
|
60
|
-
case "firmware_verify_failed":
|
|
61
|
-
return "Firmware verification failed — please try again or contact support"
|
|
62
|
-
case "apk_verify_failed":
|
|
63
|
-
return "Update verification failed — please try again or contact support"
|
|
64
|
-
case "install_failed":
|
|
65
|
-
return "Install failed — please try again"
|
|
66
|
-
default:
|
|
67
|
-
return error || "Update failed"
|
|
68
|
-
}
|
|
118
|
+
/** English message for a glasses-reported failure code. Never echoes the raw code. */
|
|
119
|
+
export function getOtaErrorMessage(error?: string | null): string {
|
|
120
|
+
return OTA_ERROR_ENGLISH_COPY[otaErrorCopyKey(error)]
|
|
69
121
|
}
|
|
70
122
|
|
|
71
123
|
/**
|
|
@@ -853,7 +853,12 @@ class OtaInstallCoordinator {
|
|
|
853
853
|
// suppress ota_start and hang the fresh downgrade until the global timeout).
|
|
854
854
|
this.otaStartOwnership?.outcome === "acknowledged" &&
|
|
855
855
|
otaStatus?.stepType === "apk" &&
|
|
856
|
-
otaStatus.phase === "install"
|
|
856
|
+
otaStatus.phase === "install" &&
|
|
857
|
+
// Only a live install arms the latch. A terminal failed status keeps stepType/phase
|
|
858
|
+
// apk/install and stays in the store, so without this gate every later pass (any
|
|
859
|
+
// unrelated store change) would re-arm the latch and the non-ownership block below
|
|
860
|
+
// would release it again, flooding the log with paired enter/release lines.
|
|
861
|
+
otaStatus.status === "in_progress"
|
|
857
862
|
) {
|
|
858
863
|
console.log("[OTA_PROGRESS] version-change: apk install started — entering detour wait")
|
|
859
864
|
this.versionChangeInstallStarted = true
|
|
@@ -102,6 +102,14 @@ export interface StartUnmanagedOptions {
|
|
|
102
102
|
/** Optional Bearer token for WHIP Authorization (custom authenticated endpoints). */
|
|
103
103
|
authToken?: string
|
|
104
104
|
captureAudio?: boolean
|
|
105
|
+
/**
|
|
106
|
+
* ICE configuration for the glasses' publisher. SoftAP passes `{stun: ""}`, which puts the
|
|
107
|
+
* glasses in host-only mode: there is no route from the hotspot to a STUN server, so a
|
|
108
|
+
* configured one would only add doomed gathering to every call.
|
|
109
|
+
*/
|
|
110
|
+
ice?: StreamStartRequest["ice"]
|
|
111
|
+
/** Correlation id echoed by the glasses into their own logs. See softapTrace. */
|
|
112
|
+
traceId?: string
|
|
105
113
|
}
|
|
106
114
|
|
|
107
115
|
export interface StartManagedOptions {
|
|
@@ -364,6 +372,8 @@ export class PhoneStreamCoordinator {
|
|
|
364
372
|
...(opts.audio !== undefined ? {audio: opts.audio} : {}),
|
|
365
373
|
...(opts.authToken ? {authToken: opts.authToken} : {}),
|
|
366
374
|
...(typeof opts.captureAudio === "boolean" ? {captureAudio: opts.captureAudio} : {}),
|
|
375
|
+
...(opts.ice !== undefined ? {ice: opts.ice} : {}),
|
|
376
|
+
...(opts.traceId ? {traceId: opts.traceId} : {}),
|
|
367
377
|
})
|
|
368
378
|
const result = publisherStartResult(streamId, event)
|
|
369
379
|
this.startLifecycle(streamId)
|