@mentra/miniapp 0.3.0-dev.0 → 3.1.0-dev.10
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 +8 -6
- package/dist/background/index.d.ts +7 -4
- package/dist/background/index.d.ts.map +1 -1
- package/dist/background/index.js +1 -0
- package/dist/background/index.js.map +1 -1
- package/dist/background/register.d.ts +3 -2
- package/dist/background/register.d.ts.map +1 -1
- package/dist/background/register.js.map +1 -1
- package/dist/hardware.d.ts +24 -0
- package/dist/hardware.d.ts.map +1 -0
- package/dist/hardware.js +20 -0
- package/dist/hardware.js.map +1 -0
- package/dist/index.d.ts +8 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/modules/camera.d.ts +50 -4
- package/dist/modules/camera.d.ts.map +1 -1
- package/dist/modules/camera.js +20 -5
- package/dist/modules/camera.js.map +1 -1
- package/dist/modules/events.d.ts +10 -17
- package/dist/modules/events.d.ts.map +1 -1
- package/dist/modules/events.js +56 -11
- package/dist/modules/events.js.map +1 -1
- package/dist/modules/glasses.d.ts +8 -0
- package/dist/modules/glasses.d.ts.map +1 -1
- package/dist/modules/glasses.js +13 -0
- package/dist/modules/glasses.js.map +1 -1
- package/dist/modules/languages.d.ts +42 -0
- package/dist/modules/languages.d.ts.map +1 -0
- package/dist/modules/languages.js +57 -0
- package/dist/modules/languages.js.map +1 -0
- package/dist/modules/mic.d.ts +17 -0
- package/dist/modules/mic.d.ts.map +1 -1
- package/dist/modules/mic.js +28 -1
- package/dist/modules/mic.js.map +1 -1
- package/dist/modules/miniapps.d.ts +2 -0
- package/dist/modules/miniapps.d.ts.map +1 -1
- package/dist/modules/miniapps.js.map +1 -1
- package/dist/modules/phone.d.ts +38 -4
- package/dist/modules/phone.d.ts.map +1 -1
- package/dist/modules/phone.js +22 -6
- package/dist/modules/phone.js.map +1 -1
- package/dist/modules/speaker.d.ts +12 -0
- package/dist/modules/speaker.d.ts.map +1 -1
- package/dist/modules/speaker.js +7 -8
- package/dist/modules/speaker.js.map +1 -1
- package/dist/modules/stream.d.ts +6 -2
- package/dist/modules/stream.d.ts.map +1 -1
- package/dist/modules/stream.js +1 -0
- package/dist/modules/stream.js.map +1 -1
- package/dist/modules/system.d.ts +20 -1
- package/dist/modules/system.d.ts.map +1 -1
- package/dist/modules/system.js +13 -1
- package/dist/modules/system.js.map +1 -1
- package/dist/modules/transcription.d.ts +35 -8
- package/dist/modules/transcription.d.ts.map +1 -1
- package/dist/modules/transcription.js +32 -12
- package/dist/modules/transcription.js.map +1 -1
- package/dist/modules/translation.d.ts +9 -3
- package/dist/modules/translation.d.ts.map +1 -1
- package/dist/modules/translation.js +5 -3
- package/dist/modules/translation.js.map +1 -1
- package/dist/protocol.d.ts +24 -2
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +24 -2
- package/dist/protocol.js.map +1 -1
- package/dist/react/capsuleHeaderLayout.d.ts +3 -0
- package/dist/react/capsuleHeaderLayout.d.ts.map +1 -0
- package/dist/react/capsuleHeaderLayout.js +5 -0
- package/dist/react/capsuleHeaderLayout.js.map +1 -0
- package/dist/react/useCapsuleHeaderStyle.d.ts.map +1 -1
- package/dist/react/useCapsuleHeaderStyle.js +6 -1
- package/dist/react/useCapsuleHeaderStyle.js.map +1 -1
- package/dist/session.d.ts +5 -3
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +7 -3
- package/dist/session.js.map +1 -1
- package/dist/sub-path-types.test-d.d.ts +12 -0
- package/dist/sub-path-types.test-d.d.ts.map +1 -1
- package/dist/sub-path-types.test-d.js.map +1 -1
- package/dist/transport/mock.js +5 -0
- package/dist/transport/mock.js.map +1 -1
- package/package.json +16 -3
- package/src/background/index.ts +18 -4
- package/src/background/register.ts +10 -3
- package/src/hardware.ts +25 -0
- package/src/index.ts +23 -9
- package/src/modules/camera.ts +75 -13
- package/src/modules/events.ts +54 -27
- package/src/modules/glasses.ts +14 -0
- package/src/modules/languages.ts +72 -0
- package/src/modules/mic.ts +30 -1
- package/src/modules/miniapps.ts +2 -0
- package/src/modules/phone.ts +59 -24
- package/src/modules/speaker.ts +21 -2
- package/src/modules/stream.ts +7 -2
- package/src/modules/system.ts +26 -1
- package/src/modules/transcription.ts +63 -16
- package/src/modules/translation.ts +22 -8
- package/src/protocol.ts +29 -3
- package/src/react/capsuleHeaderLayout.ts +11 -0
- package/src/react/useCapsuleHeaderStyle.ts +6 -1
- package/src/session.ts +19 -7
- package/src/sub-path-types.test-d.ts +12 -1
- package/src/transport/mock.ts +7 -0
package/src/modules/speaker.ts
CHANGED
|
@@ -50,6 +50,17 @@ export interface SpeakOptions {
|
|
|
50
50
|
voice_settings?: Record<string, unknown>
|
|
51
51
|
volume?: number
|
|
52
52
|
stopOtherAudio?: boolean
|
|
53
|
+
/**
|
|
54
|
+
* Normalize display-oriented markup and symbols into natural spoken words.
|
|
55
|
+
* Defaults to true. Set false when exact characters must reach the TTS engine.
|
|
56
|
+
*/
|
|
57
|
+
enableSanitization?: boolean
|
|
58
|
+
/**
|
|
59
|
+
* Force this call to use on-device offline TTS, skipping cloud TTS
|
|
60
|
+
* entirely — even when cloud is connected. Rejects with `TTS_LOCAL_UNAVAILABLE`
|
|
61
|
+
* if the offline model isn't ready instead of falling back to cloud.
|
|
62
|
+
*/
|
|
63
|
+
forceLocal?: boolean
|
|
53
64
|
}
|
|
54
65
|
|
|
55
66
|
export interface SpeakResult {
|
|
@@ -244,7 +255,13 @@ export class SpeakerModule {
|
|
|
244
255
|
* Rejects with a MiniappRequestError containing a `code` field on cloud-side
|
|
245
256
|
* TTS failures: `TTS_TEXT_TOO_LONG`, `TTS_INVALID_VOICE`, `TTS_UPSTREAM_ERROR`.
|
|
246
257
|
*/
|
|
247
|
-
async speak(text: string, options
|
|
258
|
+
async speak(text: string, options?: SpeakOptions): Promise<SpeakResult>
|
|
259
|
+
async speak(sentences: string[], options?: SpeakOptions): Promise<SpeakResult>
|
|
260
|
+
async speak(text: string | string[], options: SpeakOptions = {}): Promise<SpeakResult> {
|
|
261
|
+
const normalized = Array.isArray(text) ? text.map((sentence) => sentence.trim()).filter(Boolean) : text.trim()
|
|
262
|
+
if ((Array.isArray(normalized) && normalized.length === 0) || normalized === "") {
|
|
263
|
+
throw {code: MiniappErrorCode.INTERNAL, message: "speak requires at least one non-empty sentence"}
|
|
264
|
+
}
|
|
248
265
|
try {
|
|
249
266
|
// Like play(): resolves only when TTS playback completes, so opt out of the
|
|
250
267
|
// default request timeout (long text can outlast it). Settled by the host
|
|
@@ -252,11 +269,13 @@ export class SpeakerModule {
|
|
|
252
269
|
const result = await this.session.sendRequest<SpeakResult | null>(
|
|
253
270
|
{
|
|
254
271
|
type: MiniappRequestType.SPEAK,
|
|
255
|
-
text,
|
|
272
|
+
text: normalized,
|
|
256
273
|
voice_id: options.voice_id,
|
|
257
274
|
voice_settings: options.voice_settings,
|
|
258
275
|
volume: options.volume,
|
|
259
276
|
stopOtherAudio: options.stopOtherAudio ?? false,
|
|
277
|
+
enableSanitization: options.enableSanitization ?? true,
|
|
278
|
+
forceLocal: options.forceLocal ?? false,
|
|
260
279
|
},
|
|
261
280
|
{timeoutMs: 0},
|
|
262
281
|
)
|
package/src/modules/stream.ts
CHANGED
|
@@ -77,8 +77,12 @@ export interface StartStreamOptions {
|
|
|
77
77
|
* and automatic recording.
|
|
78
78
|
* - "whip": sub-second WebRTC playback (use `webrtcUrl`/WHEP), but no
|
|
79
79
|
* HLS/DASH and no recording.
|
|
80
|
+
* - "rtmp": RTMPS ingest over TCP 443 → HLS playback. Use when WHIP/SRT
|
|
81
|
+
* UDP is blocked (then play `hlsUrl`, not WHEP).
|
|
80
82
|
*/
|
|
81
|
-
ingest?: "srt" | "whip"
|
|
83
|
+
ingest?: "srt" | "whip" | "rtmp"
|
|
84
|
+
/** Optional Bearer token for direct WHIP Authorization (custom authenticated endpoints). */
|
|
85
|
+
authToken?: string
|
|
82
86
|
}
|
|
83
87
|
|
|
84
88
|
export interface StreamResult {
|
|
@@ -98,7 +102,7 @@ export interface StreamResult {
|
|
|
98
102
|
webrtcUrl?: string
|
|
99
103
|
}
|
|
100
104
|
|
|
101
|
-
/** Live encoder telemetry
|
|
105
|
+
/** Live encoder and device telemetry emitted periodically by supported glasses firmware. */
|
|
102
106
|
export interface StreamLiveStats {
|
|
103
107
|
/** Current encoded video bitrate in bits per second. */
|
|
104
108
|
bitrate?: number
|
|
@@ -141,6 +145,7 @@ export class StreamModule {
|
|
|
141
145
|
video: options.video,
|
|
142
146
|
audio: options.audio,
|
|
143
147
|
sound: options.sound ?? true,
|
|
148
|
+
...(options.authToken ? {authToken: options.authToken} : {}),
|
|
144
149
|
})
|
|
145
150
|
}
|
|
146
151
|
return this.session.sendRequest<StreamResult>({
|
package/src/modules/system.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview SystemModule — OS-level utilities (share, open URL, clipboard, download).
|
|
2
|
+
* @fileoverview SystemModule — OS-level utilities (share, open URL, clipboard, download, QR scan).
|
|
3
3
|
*
|
|
4
4
|
* These bridge to native phone capabilities via LocalMiniappRuntime.
|
|
5
5
|
*/
|
|
@@ -37,6 +37,15 @@ export interface DownloadResult {
|
|
|
37
37
|
cancelled?: boolean
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
export interface ScanQrOptions {
|
|
41
|
+
/** Header title on the phone scanner overlay. */
|
|
42
|
+
title?: string
|
|
43
|
+
/** Hint shown over the camera viewfinder. */
|
|
44
|
+
hint?: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type ScanQrResult = {data: string; cancelled?: false} | {cancelled: true; data?: undefined}
|
|
48
|
+
|
|
40
49
|
export class SystemModule {
|
|
41
50
|
constructor(private readonly session: MiniappSession) {}
|
|
42
51
|
|
|
@@ -73,4 +82,20 @@ export class SystemModule {
|
|
|
73
82
|
})
|
|
74
83
|
return result ?? { success: false }
|
|
75
84
|
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Open the phone camera QR scanner as a host overlay. Does not unmount the
|
|
88
|
+
* miniapp (so live sessions survive the scan). Resolves with the raw QR
|
|
89
|
+
* payload, or `{cancelled: true}` if the user dismisses the scanner.
|
|
90
|
+
*/
|
|
91
|
+
async scanQr(options: ScanQrOptions = {}): Promise<ScanQrResult> {
|
|
92
|
+
const result = await this.session.sendRequest<ScanQrResult>(
|
|
93
|
+
{
|
|
94
|
+
type: MiniappRequestType.SCAN_QR,
|
|
95
|
+
...options,
|
|
96
|
+
},
|
|
97
|
+
{timeoutMs: 0},
|
|
98
|
+
)
|
|
99
|
+
return result ?? { cancelled: true }
|
|
100
|
+
}
|
|
76
101
|
}
|
|
@@ -21,17 +21,33 @@
|
|
|
21
21
|
import {MiniappRequestType, MiniappStreamType} from "../protocol"
|
|
22
22
|
import {MiniappSession} from "../session"
|
|
23
23
|
import type {TranscriptionData, UnsubscribeFn} from "./events"
|
|
24
|
+
import {requireLanguageHint, requireTranscriptionLanguage} from "./languages"
|
|
25
|
+
import type {LanguageHint, TranscriptionLanguage} from "./languages"
|
|
24
26
|
|
|
25
27
|
/** Configuration for cloud-side transcription behavior. */
|
|
26
28
|
export interface TranscriptionConfig {
|
|
27
|
-
/**
|
|
28
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Language hints to improve auto-detection accuracy. Bare ISO 639-1 codes
|
|
31
|
+
* from the registry (e.g. ["en", "ja"]); registry BCP-47 tags are accepted
|
|
32
|
+
* and normalized ("en-US" -> "en"). Anything else throws
|
|
33
|
+
* `MiniappValidationError`.
|
|
34
|
+
*/
|
|
35
|
+
languageHints?: LanguageHint[]
|
|
29
36
|
/** Custom vocabulary / boosted terms (e.g. ["MentraOS", "HIPAA"]). */
|
|
30
37
|
vocabulary?: string[]
|
|
31
38
|
/** Enable speaker diarisation. Defaults vary by provider. */
|
|
32
39
|
diarization?: boolean
|
|
33
40
|
}
|
|
34
41
|
|
|
42
|
+
export interface TranscriptionOptions {
|
|
43
|
+
/**
|
|
44
|
+
* Use only on-device STT for this listener; never deliver cloud transcripts.
|
|
45
|
+
* Other listeners on the same stream keep their own routing choice. Requires
|
|
46
|
+
* a downloaded local STT model.
|
|
47
|
+
*/
|
|
48
|
+
forceLocal?: boolean
|
|
49
|
+
}
|
|
50
|
+
|
|
35
51
|
export class TranscriptionModule {
|
|
36
52
|
private readonly unsubs = new Set<UnsubscribeFn>()
|
|
37
53
|
private currentConfig: TranscriptionConfig | null = null
|
|
@@ -46,9 +62,9 @@ export class TranscriptionModule {
|
|
|
46
62
|
* (handlers on `transcription:auto` receive any `transcription:<lang>`
|
|
47
63
|
* event) preserves existing semantics.
|
|
48
64
|
*/
|
|
49
|
-
on(handler: (data: TranscriptionData) => void): UnsubscribeFn {
|
|
65
|
+
on(handler: (data: TranscriptionData) => void, options: TranscriptionOptions = {}): UnsubscribeFn {
|
|
50
66
|
return this.track(
|
|
51
|
-
this.session._subscribe(`${MiniappStreamType.TRANSCRIPTION}:auto`, handler as (data: unknown) => void),
|
|
67
|
+
this.session._subscribe(`${MiniappStreamType.TRANSCRIPTION}:auto`, handler as (data: unknown) => void, options),
|
|
52
68
|
)
|
|
53
69
|
}
|
|
54
70
|
|
|
@@ -56,17 +72,34 @@ export class TranscriptionModule {
|
|
|
56
72
|
* Subscribe to transcription for one or more specific languages. Each call
|
|
57
73
|
* is independent; multiple can be active simultaneously.
|
|
58
74
|
*
|
|
59
|
-
*
|
|
75
|
+
* Languages are validated against the platform registry
|
|
76
|
+
* (`SUPPORTED_TRANSCRIPTION_LANGUAGES`): registry BCP-47 tags pass through,
|
|
77
|
+
* bare registry codes canonicalize ("fr" -> "fr-FR"), anything else throws
|
|
78
|
+
* `MiniappValidationError` immediately — an invalid language must never
|
|
79
|
+
* become a subscription that silently transcribes the wrong thing
|
|
80
|
+
* (OS-1746 / issue 021).
|
|
81
|
+
*
|
|
82
|
+
* @param language - Registry tag(s), e.g. `"en-US"` or `["en-US", "es-ES"]`.
|
|
60
83
|
* @param handler - Called for every event in any of the listed languages.
|
|
61
84
|
*/
|
|
62
|
-
forLanguage(
|
|
63
|
-
|
|
85
|
+
forLanguage(
|
|
86
|
+
language: TranscriptionLanguage | TranscriptionLanguage[],
|
|
87
|
+
handler: (data: TranscriptionData) => void,
|
|
88
|
+
options: TranscriptionOptions = {},
|
|
89
|
+
): UnsubscribeFn {
|
|
90
|
+
const langs = (Array.isArray(language) ? language : [language]).map((lang) =>
|
|
91
|
+
requireTranscriptionLanguage(lang, "transcription.forLanguage(language)"),
|
|
92
|
+
)
|
|
64
93
|
if (langs.length === 0) return () => {}
|
|
65
94
|
|
|
66
95
|
const unsubs: UnsubscribeFn[] = []
|
|
67
96
|
for (const lang of langs) {
|
|
68
97
|
unsubs.push(
|
|
69
|
-
this.session._subscribe(
|
|
98
|
+
this.session._subscribe(
|
|
99
|
+
`${MiniappStreamType.TRANSCRIPTION}:${lang}`,
|
|
100
|
+
handler as (data: unknown) => void,
|
|
101
|
+
options,
|
|
102
|
+
),
|
|
70
103
|
)
|
|
71
104
|
}
|
|
72
105
|
const combined: UnsubscribeFn = () => {
|
|
@@ -83,15 +116,29 @@ export class TranscriptionModule {
|
|
|
83
116
|
|
|
84
117
|
/**
|
|
85
118
|
* Apply transcription configuration (language hints, custom vocabulary,
|
|
86
|
-
* diarisation toggle).
|
|
87
|
-
*
|
|
119
|
+
* diarisation toggle). Hints are validated against the registry and
|
|
120
|
+
* normalized to bare codes ("en-US" -> "en"); unknown values throw
|
|
121
|
+
* `MiniappValidationError` synchronously.
|
|
122
|
+
*
|
|
123
|
+
* Sent to the phone runtime as a REQUEST (not a one-shot) so the returned
|
|
124
|
+
* promise rejects if the runtime cannot apply it — a config that the
|
|
125
|
+
* platform ignores must be loud, not silent (issue 021 S3). Cached locally
|
|
126
|
+
* so the runtime can re-apply on reconnect.
|
|
88
127
|
*/
|
|
89
|
-
configure(config: TranscriptionConfig): void {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
128
|
+
configure(config: TranscriptionConfig): Promise<void> {
|
|
129
|
+
const normalized: TranscriptionConfig = {
|
|
130
|
+
...config,
|
|
131
|
+
languageHints: config.languageHints?.map((hint) =>
|
|
132
|
+
requireLanguageHint(hint, "transcription.configure(languageHints)"),
|
|
133
|
+
),
|
|
134
|
+
}
|
|
135
|
+
this.currentConfig = {...normalized}
|
|
136
|
+
return this.session
|
|
137
|
+
.sendRequest<void>({
|
|
138
|
+
type: MiniappRequestType.TRANSCRIPTION_CONFIG,
|
|
139
|
+
config: {...normalized},
|
|
140
|
+
})
|
|
141
|
+
.then(() => undefined)
|
|
95
142
|
}
|
|
96
143
|
|
|
97
144
|
/** Tear down every transcription subscription this module owns. */
|
|
@@ -20,9 +20,17 @@
|
|
|
20
20
|
import {MiniappStreamType} from "../protocol"
|
|
21
21
|
import {MiniappSession} from "../session"
|
|
22
22
|
import type {TranslationData, UnsubscribeFn} from "./events"
|
|
23
|
+
import {requireTranscriptionLanguage} from "./languages"
|
|
24
|
+
import type {TranscriptionLanguage} from "./languages"
|
|
23
25
|
|
|
24
26
|
export type TranslationHandler = (data: TranslationData) => void
|
|
25
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Translation source: a registry language, or "auto" to let the cloud detect
|
|
30
|
+
* the spoken language.
|
|
31
|
+
*/
|
|
32
|
+
export type TranslationSource = TranscriptionLanguage | "auto"
|
|
33
|
+
|
|
26
34
|
export class TranslationModule {
|
|
27
35
|
private readonly unsubs = new Set<UnsubscribeFn>()
|
|
28
36
|
|
|
@@ -51,8 +59,10 @@ export class TranslationModule {
|
|
|
51
59
|
* regardless of source. Useful for "I'm a Spanish speaker, translate
|
|
52
60
|
* whatever I hear to Spanish."
|
|
53
61
|
*/
|
|
54
|
-
to(target:
|
|
55
|
-
const targets = Array.isArray(target) ? target : [target]
|
|
62
|
+
to(target: TranscriptionLanguage | TranscriptionLanguage[], handler: TranslationHandler): UnsubscribeFn {
|
|
63
|
+
const targets = (Array.isArray(target) ? target : [target]).map((t) =>
|
|
64
|
+
requireTranscriptionLanguage(t, "translation.to(target)"),
|
|
65
|
+
)
|
|
56
66
|
if (targets.length === 0) return () => {}
|
|
57
67
|
const unsubs: UnsubscribeFn[] = []
|
|
58
68
|
for (const t of targets) {
|
|
@@ -81,17 +91,21 @@ export class TranslationModule {
|
|
|
81
91
|
* from the same source.
|
|
82
92
|
*/
|
|
83
93
|
fromTo(
|
|
84
|
-
source:
|
|
85
|
-
target:
|
|
94
|
+
source: TranslationSource,
|
|
95
|
+
target: TranscriptionLanguage | TranscriptionLanguage[],
|
|
86
96
|
handler: TranslationHandler,
|
|
87
97
|
): UnsubscribeFn {
|
|
88
|
-
const
|
|
98
|
+
const validSource =
|
|
99
|
+
source === "auto" ? "auto" : requireTranscriptionLanguage(source, "translation.fromTo(source)")
|
|
100
|
+
const targets = (Array.isArray(target) ? target : [target]).map((t) =>
|
|
101
|
+
requireTranscriptionLanguage(t, "translation.fromTo(target)"),
|
|
102
|
+
)
|
|
89
103
|
if (targets.length === 0) return () => {}
|
|
90
104
|
const unsubs: UnsubscribeFn[] = []
|
|
91
105
|
for (const t of targets) {
|
|
92
106
|
unsubs.push(
|
|
93
107
|
this.session._subscribe(
|
|
94
|
-
`${MiniappStreamType.TRANSLATION}:${
|
|
108
|
+
`${MiniappStreamType.TRANSLATION}:${validSource}:${t}`,
|
|
95
109
|
handler as (data: unknown) => void,
|
|
96
110
|
),
|
|
97
111
|
)
|
|
@@ -113,8 +127,8 @@ export class TranslationModule {
|
|
|
113
127
|
* SDK v3 parity. This alias will be removed in a future release.
|
|
114
128
|
*/
|
|
115
129
|
forLanguagePair(
|
|
116
|
-
fromLang:
|
|
117
|
-
toLang:
|
|
130
|
+
fromLang: TranslationSource,
|
|
131
|
+
toLang: TranscriptionLanguage,
|
|
118
132
|
handler: TranslationHandler,
|
|
119
133
|
): UnsubscribeFn {
|
|
120
134
|
return this.fromTo(fromLang, toLang, handler)
|
package/src/protocol.ts
CHANGED
|
@@ -60,6 +60,9 @@ export enum MiniappRequestType {
|
|
|
60
60
|
/** One-shot location poll. */
|
|
61
61
|
LOCATION_POLL = "miniapp_location_poll",
|
|
62
62
|
|
|
63
|
+
/** Read a bounded snapshot of phone calendar events. */
|
|
64
|
+
CALENDAR_LIST_EVENTS = "miniapp_calendar_list_events",
|
|
65
|
+
|
|
63
66
|
/** Start a turn-by-turn navigation trip. Android only. */
|
|
64
67
|
NAVIGATION_START = "miniapp_navigation_start",
|
|
65
68
|
/** Stop the active navigation trip (if any). */
|
|
@@ -114,6 +117,17 @@ export enum MiniappRequestType {
|
|
|
114
117
|
*/
|
|
115
118
|
IMU_SET_ENABLED = "miniapp_imu_set_enabled",
|
|
116
119
|
|
|
120
|
+
/** Explicitly enable/disable glasses-side VAD (GX8002). Mirrors the app's mic settings toggle. */
|
|
121
|
+
MIC_SET_VAD_ENABLED = "miniapp_mic_set_vad_enabled",
|
|
122
|
+
/** Explicitly enable/disable the center-mic loudness gate ("Barrier"). */
|
|
123
|
+
MIC_SET_LOUDNESS_GATE_ENABLED = "miniapp_mic_set_loudness_gate_enabled",
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Enable or disable Wi-Fi ADB (wireless debugging) on Mentra Live.
|
|
127
|
+
* SYSTEM-only — host rejects with NOT_PERMITTED for non-system callers.
|
|
128
|
+
*/
|
|
129
|
+
SET_WIFI_ADB_STATE = "miniapp_set_wifi_adb_state",
|
|
130
|
+
|
|
117
131
|
/** Share content via the OS share sheet. */
|
|
118
132
|
SHARE = "miniapp_share",
|
|
119
133
|
/** Open a URL in the system browser. */
|
|
@@ -122,6 +136,11 @@ export enum MiniappRequestType {
|
|
|
122
136
|
COPY_CLIPBOARD = "miniapp_copy_clipboard",
|
|
123
137
|
/** Download a file (triggers OS share sheet for save location). */
|
|
124
138
|
DOWNLOAD = "miniapp_download",
|
|
139
|
+
/**
|
|
140
|
+
* Open a phone-camera QR scanner overlay. Host must not unmount the miniapp
|
|
141
|
+
* (clearing foreground fires UI_CLOSE and can hang up live sessions).
|
|
142
|
+
*/
|
|
143
|
+
SCAN_QR = "miniapp_scan_qr",
|
|
125
144
|
|
|
126
145
|
/**
|
|
127
146
|
* Phone-local persistent binary blob storage, scoped to (userId, packageName).
|
|
@@ -300,8 +319,6 @@ export enum MiniappStreamType {
|
|
|
300
319
|
* fire. See agents/miniapp-speaker-state-and-notif-dismissed-plan.md.
|
|
301
320
|
*/
|
|
302
321
|
PHONE_NOTIFICATION_DISMISSED = "phone_notification_dismissed",
|
|
303
|
-
CALENDAR_EVENT = "calendar_event",
|
|
304
|
-
|
|
305
322
|
// Photos, streaming
|
|
306
323
|
PHOTO_TAKEN = "photo_taken",
|
|
307
324
|
STREAM_STATUS = "stream_status",
|
|
@@ -318,6 +335,9 @@ export enum MiniappErrorCode {
|
|
|
318
335
|
/** The miniapp subscribed to a stream whose required permission wasn't in its manifest. */
|
|
319
336
|
PERMISSION_NOT_DECLARED = "PERMISSION_NOT_DECLARED",
|
|
320
337
|
|
|
338
|
+
/** The required phone OS permission is not currently granted. */
|
|
339
|
+
PERMISSION_DENIED = "PERMISSION_DENIED",
|
|
340
|
+
|
|
321
341
|
/** Request routed to a method that isn't supported yet. */
|
|
322
342
|
NOT_IMPLEMENTED = "NOT_IMPLEMENTED",
|
|
323
343
|
|
|
@@ -332,11 +352,17 @@ export enum MiniappErrorCode {
|
|
|
332
352
|
TTS_INVALID_VOICE = "TTS_INVALID_VOICE",
|
|
333
353
|
TTS_UPSTREAM_ERROR = "TTS_UPSTREAM_ERROR",
|
|
334
354
|
|
|
355
|
+
/** `speak({forceLocal: true})` but the on-device offline TTS model isn't downloaded/ready. */
|
|
356
|
+
TTS_LOCAL_UNAVAILABLE = "TTS_LOCAL_UNAVAILABLE",
|
|
357
|
+
|
|
335
358
|
/** Not connected / pre-ACK and transport closed. */
|
|
336
359
|
NOT_CONNECTED = "NOT_CONNECTED",
|
|
337
360
|
|
|
338
361
|
// ----- Inter-miniapp interop -----
|
|
339
|
-
/**
|
|
362
|
+
/**
|
|
363
|
+
* Caller is not a system app — SYSTEM-only APIs (interop list/start/stop/invoke,
|
|
364
|
+
* setWifiAdbState) reject with this code.
|
|
365
|
+
*/
|
|
340
366
|
NOT_PERMITTED = "NOT_PERMITTED",
|
|
341
367
|
/** Target miniapp is not installed. */
|
|
342
368
|
APP_NOT_FOUND = "APP_NOT_FOUND",
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type {MiniappCapsuleMenuRect} from "../globals"
|
|
2
|
+
|
|
3
|
+
export function getCapsuleHeaderPaddingRight(
|
|
4
|
+
capsuleMenu: MiniappCapsuleMenuRect,
|
|
5
|
+
viewportWidth: number,
|
|
6
|
+
safeAreaRight: number,
|
|
7
|
+
rightGap: number,
|
|
8
|
+
): number {
|
|
9
|
+
const capsuleOffsetFromContentRight = viewportWidth - capsuleMenu.left - safeAreaRight
|
|
10
|
+
return Math.max(rightGap, capsuleOffsetFromContentRight + rightGap)
|
|
11
|
+
}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import type {CSSProperties} from "react"
|
|
17
17
|
|
|
18
|
+
import {getCapsuleHeaderPaddingRight} from "./capsuleHeaderLayout"
|
|
18
19
|
import {useSafeArea} from "./useSafeArea"
|
|
19
20
|
|
|
20
21
|
export interface UseCapsuleHeaderStyleOptions {
|
|
@@ -39,7 +40,11 @@ export function useCapsuleHeaderStyle(
|
|
|
39
40
|
? capsuleMenu.top + capsuleMenu.height / 2 - insets.top
|
|
40
41
|
: fallbackMarginTop + height / 2
|
|
41
42
|
const marginTop = Math.max(0, center - height / 2)
|
|
42
|
-
const paddingRight = capsuleMenu
|
|
43
|
+
const paddingRight = capsuleMenu
|
|
44
|
+
? typeof window === "undefined"
|
|
45
|
+
? capsuleMenu.width + rightGap
|
|
46
|
+
: getCapsuleHeaderPaddingRight(capsuleMenu, window.innerWidth, insets.right, rightGap)
|
|
47
|
+
: leftPadding
|
|
43
48
|
|
|
44
49
|
return {
|
|
45
50
|
display: "flex",
|
package/src/session.ts
CHANGED
|
@@ -24,7 +24,7 @@ import {AuthModule} from "./modules/auth"
|
|
|
24
24
|
import {CloudModule} from "./modules/cloud"
|
|
25
25
|
import {DashboardAPI} from "./modules/dashboard"
|
|
26
26
|
import {DisplayManager} from "./modules/display"
|
|
27
|
-
import {EventManager, type UnsubscribeFn} from "./modules/events"
|
|
27
|
+
import {EventManager, type TranscriptionEventRoute, type UnsubscribeFn} from "./modules/events"
|
|
28
28
|
import {GlassesModule} from "./modules/glasses"
|
|
29
29
|
import {HeadingModule} from "./modules/heading"
|
|
30
30
|
import {ImuModule} from "./modules/imu"
|
|
@@ -194,7 +194,11 @@ type SessionEmitterEvents = {
|
|
|
194
194
|
auth: (auth: MiniappAuthState) => void
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
-
|
|
197
|
+
// The default preserves the pre-channel-registry behavior for code that creates
|
|
198
|
+
// or accepts a bare MiniappSession. `registerMiniapp<Channels>` supplies the
|
|
199
|
+
// concrete mapping for scaffolded miniapps.
|
|
200
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
201
|
+
export class MiniappSession<TChannels extends object = any> {
|
|
198
202
|
public readonly auth: AuthModule
|
|
199
203
|
public readonly display: DisplayManager
|
|
200
204
|
/**
|
|
@@ -238,7 +242,7 @@ export class MiniappSession {
|
|
|
238
242
|
* with inverted buffering policy (background drops when no WebView is
|
|
239
243
|
* bound; the WebView buffers until ready).
|
|
240
244
|
*/
|
|
241
|
-
public readonly ui: UIModule
|
|
245
|
+
public readonly ui: UIModule<TChannels>
|
|
242
246
|
/**
|
|
243
247
|
* Inter-miniapp lifecycle + discovery (list / start / stop). SYSTEM-only —
|
|
244
248
|
* calls reject with NOT_PERMITTED unless this miniapp is a system app.
|
|
@@ -324,7 +328,7 @@ export class MiniappSession {
|
|
|
324
328
|
this.system = new SystemModule(this)
|
|
325
329
|
this.transcription = new TranscriptionModule(this)
|
|
326
330
|
this.translation = new TranslationModule(this)
|
|
327
|
-
this.ui = new UIModuleImpl(this)
|
|
331
|
+
this.ui = new UIModuleImpl<TChannels>(this)
|
|
328
332
|
this.miniapps = new MiniappsModule(this)
|
|
329
333
|
this.actions = new ActionsModule(this)
|
|
330
334
|
}
|
|
@@ -392,8 +396,12 @@ export class MiniappSession {
|
|
|
392
396
|
* session.transcription.on(...)
|
|
393
397
|
* etc. instead."
|
|
394
398
|
*/
|
|
395
|
-
_subscribe(
|
|
396
|
-
|
|
399
|
+
_subscribe(
|
|
400
|
+
streamType: string,
|
|
401
|
+
handler: (data: unknown) => void,
|
|
402
|
+
options: {forceLocal?: boolean} = {},
|
|
403
|
+
): UnsubscribeFn {
|
|
404
|
+
return this.events.subscribe(streamType, handler, options)
|
|
397
405
|
}
|
|
398
406
|
|
|
399
407
|
// -------------------------------------------------------------------------
|
|
@@ -677,7 +685,11 @@ export class MiniappSession {
|
|
|
677
685
|
case MiniappResponseType.EVENT: {
|
|
678
686
|
const streamType = payload.streamType as string | undefined
|
|
679
687
|
if (!streamType) return
|
|
680
|
-
this.events._forwardEvent(
|
|
688
|
+
this.events._forwardEvent(
|
|
689
|
+
streamType,
|
|
690
|
+
payload.data,
|
|
691
|
+
payload.transcriptionRoute as TranscriptionEventRoute | undefined,
|
|
692
|
+
)
|
|
681
693
|
return
|
|
682
694
|
}
|
|
683
695
|
|
|
@@ -23,12 +23,23 @@
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
// ----- @mentra/miniapp/background ---------------------------------------
|
|
26
|
-
import
|
|
26
|
+
import {registerMiniapp} from "./background/index"
|
|
27
|
+
import type {MiniappSession, UIModule} from "./background/index"
|
|
27
28
|
|
|
28
29
|
// ✅ MiniappSession is reachable from the background entry.
|
|
29
30
|
const _bg: MiniappSession | undefined = undefined
|
|
30
31
|
void _bg
|
|
31
32
|
|
|
33
|
+
// ✅ registerMiniapp<Channels> carries the shared channel registry into
|
|
34
|
+
// session.ui on the background side.
|
|
35
|
+
interface _TestChannels {
|
|
36
|
+
ping: {at: number}
|
|
37
|
+
}
|
|
38
|
+
type _RegisteredHandler = Parameters<typeof registerMiniapp<_TestChannels>>[0]
|
|
39
|
+
type _RegisteredSession = Parameters<_RegisteredHandler>[0]
|
|
40
|
+
type _Assert<T extends true> = T
|
|
41
|
+
export type _TypedBackground = _Assert<_RegisteredSession["ui"] extends UIModule<_TestChannels> ? true : false>
|
|
42
|
+
|
|
32
43
|
// ----- @mentra/miniapp/ui -----------------------------------------------
|
|
33
44
|
import type {MentraUiGlobal, MentraTyped} from "./ui/index"
|
|
34
45
|
|
package/src/transport/mock.ts
CHANGED
|
@@ -226,6 +226,9 @@ function syntheticDataFor(requestId: string, requestType: string, requestPayload
|
|
|
226
226
|
// Atlantic, which makes maps/navigation unusable in the WebView.
|
|
227
227
|
return {lat: 37.7956, lng: -122.3933, accuracy: 0, timestamp: Date.now()}
|
|
228
228
|
|
|
229
|
+
case MiniappRequestType.CALENDAR_LIST_EVENTS:
|
|
230
|
+
return {events: [], truncated: false}
|
|
231
|
+
|
|
229
232
|
case MiniappRequestType.STORAGE_GET:
|
|
230
233
|
return {value: null}
|
|
231
234
|
|
|
@@ -240,8 +243,12 @@ function syntheticDataFor(requestId: string, requestType: string, requestPayload
|
|
|
240
243
|
case MiniappRequestType.COPY_CLIPBOARD:
|
|
241
244
|
case MiniappRequestType.DOWNLOAD:
|
|
242
245
|
case MiniappRequestType.REQUEST_WIFI_SETUP:
|
|
246
|
+
case MiniappRequestType.SET_WIFI_ADB_STATE:
|
|
243
247
|
return {ok: true}
|
|
244
248
|
|
|
249
|
+
case MiniappRequestType.SCAN_QR:
|
|
250
|
+
return {cancelled: true}
|
|
251
|
+
|
|
245
252
|
default:
|
|
246
253
|
return null
|
|
247
254
|
}
|