@glassly/miniapp 0.3.0-dev.1
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 +275 -0
- package/dist/background/index.d.ts +64 -0
- package/dist/background/index.d.ts.map +1 -0
- package/dist/background/index.js +26 -0
- package/dist/background/index.js.map +1 -0
- package/dist/background/register.d.ts +39 -0
- package/dist/background/register.d.ts.map +1 -0
- package/dist/background/register.js +72 -0
- package/dist/background/register.js.map +1 -0
- package/dist/dev-reload.d.ts +21 -0
- package/dist/dev-reload.d.ts.map +1 -0
- package/dist/dev-reload.js +60 -0
- package/dist/dev-reload.js.map +1 -0
- package/dist/envelope.d.ts +23 -0
- package/dist/envelope.d.ts.map +1 -0
- package/dist/envelope.js +47 -0
- package/dist/envelope.js.map +1 -0
- package/dist/globals.d.ts +54 -0
- package/dist/globals.d.ts.map +1 -0
- package/dist/globals.js +12 -0
- package/dist/globals.js.map +1 -0
- package/dist/hardware.d.ts +38 -0
- package/dist/hardware.d.ts.map +1 -0
- package/dist/hardware.js +31 -0
- package/dist/hardware.js.map +1 -0
- package/dist/index.d.ts +65 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/modules/actions.d.ts +81 -0
- package/dist/modules/actions.d.ts.map +1 -0
- package/dist/modules/actions.js +137 -0
- package/dist/modules/actions.js.map +1 -0
- package/dist/modules/ai.d.ts +211 -0
- package/dist/modules/ai.d.ts.map +1 -0
- package/dist/modules/ai.js +117 -0
- package/dist/modules/ai.js.map +1 -0
- package/dist/modules/auth.d.ts +25 -0
- package/dist/modules/auth.d.ts.map +1 -0
- package/dist/modules/auth.js +52 -0
- package/dist/modules/auth.js.map +1 -0
- package/dist/modules/base64.d.ts +16 -0
- package/dist/modules/base64.d.ts.map +1 -0
- package/dist/modules/base64.js +92 -0
- package/dist/modules/base64.js.map +1 -0
- package/dist/modules/blob.d.ts +161 -0
- package/dist/modules/blob.d.ts.map +1 -0
- package/dist/modules/blob.js +277 -0
- package/dist/modules/blob.js.map +1 -0
- package/dist/modules/camera.d.ts +183 -0
- package/dist/modules/camera.d.ts.map +1 -0
- package/dist/modules/camera.js +119 -0
- package/dist/modules/camera.js.map +1 -0
- package/dist/modules/cloud.d.ts +34 -0
- package/dist/modules/cloud.d.ts.map +1 -0
- package/dist/modules/cloud.js +70 -0
- package/dist/modules/cloud.js.map +1 -0
- package/dist/modules/dashboard.d.ts +24 -0
- package/dist/modules/dashboard.d.ts.map +1 -0
- package/dist/modules/dashboard.js +39 -0
- package/dist/modules/dashboard.js.map +1 -0
- package/dist/modules/display.d.ts +167 -0
- package/dist/modules/display.d.ts.map +1 -0
- package/dist/modules/display.js +83 -0
- package/dist/modules/display.js.map +1 -0
- package/dist/modules/events.d.ts +178 -0
- package/dist/modules/events.d.ts.map +1 -0
- package/dist/modules/events.js +168 -0
- package/dist/modules/events.js.map +1 -0
- package/dist/modules/glasses.d.ts +36 -0
- package/dist/modules/glasses.d.ts.map +1 -0
- package/dist/modules/glasses.js +52 -0
- package/dist/modules/glasses.js.map +1 -0
- package/dist/modules/heading.d.ts +16 -0
- package/dist/modules/heading.d.ts.map +1 -0
- package/dist/modules/heading.js +20 -0
- package/dist/modules/heading.js.map +1 -0
- package/dist/modules/imu.d.ts +31 -0
- package/dist/modules/imu.d.ts.map +1 -0
- package/dist/modules/imu.js +40 -0
- package/dist/modules/imu.js.map +1 -0
- package/dist/modules/input.d.ts +34 -0
- package/dist/modules/input.d.ts.map +1 -0
- package/dist/modules/input.js +50 -0
- package/dist/modules/input.js.map +1 -0
- package/dist/modules/keys.d.ts +30 -0
- package/dist/modules/keys.d.ts.map +1 -0
- package/dist/modules/keys.js +37 -0
- package/dist/modules/keys.js.map +1 -0
- 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/led.d.ts +35 -0
- package/dist/modules/led.d.ts.map +1 -0
- package/dist/modules/led.js +39 -0
- package/dist/modules/led.js.map +1 -0
- package/dist/modules/location.d.ts +27 -0
- package/dist/modules/location.d.ts.map +1 -0
- package/dist/modules/location.js +35 -0
- package/dist/modules/location.js.map +1 -0
- package/dist/modules/memory.d.ts +126 -0
- package/dist/modules/memory.d.ts.map +1 -0
- package/dist/modules/memory.js +141 -0
- package/dist/modules/memory.js.map +1 -0
- package/dist/modules/mic.d.ts +61 -0
- package/dist/modules/mic.d.ts.map +1 -0
- package/dist/modules/mic.js +94 -0
- package/dist/modules/mic.js.map +1 -0
- package/dist/modules/miniapps.d.ts +82 -0
- package/dist/modules/miniapps.d.ts.map +1 -0
- package/dist/modules/miniapps.js +58 -0
- package/dist/modules/miniapps.js.map +1 -0
- package/dist/modules/navigation.d.ts +332 -0
- package/dist/modules/navigation.d.ts.map +1 -0
- package/dist/modules/navigation.js +387 -0
- package/dist/modules/navigation.js.map +1 -0
- package/dist/modules/permissions.d.ts +58 -0
- package/dist/modules/permissions.d.ts.map +1 -0
- package/dist/modules/permissions.js +66 -0
- package/dist/modules/permissions.js.map +1 -0
- package/dist/modules/phone.d.ts +95 -0
- package/dist/modules/phone.d.ts.map +1 -0
- package/dist/modules/phone.js +110 -0
- package/dist/modules/phone.js.map +1 -0
- package/dist/modules/pivots/engine.d.ts +169 -0
- package/dist/modules/pivots/engine.d.ts.map +1 -0
- package/dist/modules/pivots/engine.js +778 -0
- package/dist/modules/pivots/engine.js.map +1 -0
- package/dist/modules/pivots/geometry.d.ts +84 -0
- package/dist/modules/pivots/geometry.d.ts.map +1 -0
- package/dist/modules/pivots/geometry.js +371 -0
- package/dist/modules/pivots/geometry.js.map +1 -0
- package/dist/modules/pivots/instructions.d.ts +139 -0
- package/dist/modules/pivots/instructions.d.ts.map +1 -0
- package/dist/modules/pivots/instructions.js +281 -0
- package/dist/modules/pivots/instructions.js.map +1 -0
- package/dist/modules/speaker.d.ts +175 -0
- package/dist/modules/speaker.d.ts.map +1 -0
- package/dist/modules/speaker.js +249 -0
- package/dist/modules/speaker.js.map +1 -0
- package/dist/modules/speakers.d.ts +29 -0
- package/dist/modules/speakers.d.ts.map +1 -0
- package/dist/modules/speakers.js +38 -0
- package/dist/modules/speakers.js.map +1 -0
- package/dist/modules/storage.d.ts +50 -0
- package/dist/modules/storage.d.ts.map +1 -0
- package/dist/modules/storage.js +99 -0
- package/dist/modules/storage.js.map +1 -0
- package/dist/modules/stream.d.ts +133 -0
- package/dist/modules/stream.d.ts.map +1 -0
- package/dist/modules/stream.js +53 -0
- package/dist/modules/stream.js.map +1 -0
- package/dist/modules/system.d.ts +71 -0
- package/dist/modules/system.d.ts.map +1 -0
- package/dist/modules/system.js +65 -0
- package/dist/modules/system.js.map +1 -0
- package/dist/modules/transcription.d.ts +99 -0
- package/dist/modules/transcription.d.ts.map +1 -0
- package/dist/modules/transcription.js +126 -0
- package/dist/modules/transcription.js.map +1 -0
- package/dist/modules/translation.d.ts +65 -0
- package/dist/modules/translation.d.ts.map +1 -0
- package/dist/modules/translation.js +121 -0
- package/dist/modules/translation.js.map +1 -0
- package/dist/modules/ui.d.ts +186 -0
- package/dist/modules/ui.d.ts.map +1 -0
- package/dist/modules/ui.js +289 -0
- package/dist/modules/ui.js.map +1 -0
- package/dist/protocol.d.ts +384 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +413 -0
- package/dist/protocol.js.map +1 -0
- package/dist/react/MiniappHeader.d.ts +44 -0
- package/dist/react/MiniappHeader.d.ts.map +1 -0
- package/dist/react/MiniappHeader.js +32 -0
- package/dist/react/MiniappHeader.js.map +1 -0
- package/dist/react/MiniappProvider.d.ts +33 -0
- package/dist/react/MiniappProvider.d.ts.map +1 -0
- package/dist/react/MiniappProvider.js +15 -0
- package/dist/react/MiniappProvider.js.map +1 -0
- 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/index.d.ts +13 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +9 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/useCapabilities.d.ts +26 -0
- package/dist/react/useCapabilities.d.ts.map +1 -0
- package/dist/react/useCapabilities.js +27 -0
- package/dist/react/useCapabilities.js.map +1 -0
- package/dist/react/useCapsuleHeaderStyle.d.ts +27 -0
- package/dist/react/useCapsuleHeaderStyle.d.ts.map +1 -0
- package/dist/react/useCapsuleHeaderStyle.js +39 -0
- package/dist/react/useCapsuleHeaderStyle.js.map +1 -0
- package/dist/react/useColorScheme.d.ts +18 -0
- package/dist/react/useColorScheme.d.ts.map +1 -0
- package/dist/react/useColorScheme.js +24 -0
- package/dist/react/useColorScheme.js.map +1 -0
- package/dist/react/useConnected.d.ts +17 -0
- package/dist/react/useConnected.d.ts.map +1 -0
- package/dist/react/useConnected.js +39 -0
- package/dist/react/useConnected.js.map +1 -0
- package/dist/react/useRpc.d.ts +29 -0
- package/dist/react/useRpc.d.ts.map +1 -0
- package/dist/react/useRpc.js +82 -0
- package/dist/react/useRpc.js.map +1 -0
- package/dist/react/useSafeArea.d.ts +21 -0
- package/dist/react/useSafeArea.d.ts.map +1 -0
- package/dist/react/useSafeArea.js +22 -0
- package/dist/react/useSafeArea.js.map +1 -0
- package/dist/session.d.ts +355 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +688 -0
- package/dist/session.js.map +1 -0
- package/dist/sub-path-types.test-d.d.ts +40 -0
- package/dist/sub-path-types.test-d.d.ts.map +1 -0
- package/dist/sub-path-types.test-d.js +37 -0
- package/dist/sub-path-types.test-d.js.map +1 -0
- package/dist/transport/auto.d.ts +35 -0
- package/dist/transport/auto.d.ts.map +1 -0
- package/dist/transport/auto.js +124 -0
- package/dist/transport/auto.js.map +1 -0
- package/dist/transport/dispatch.d.ts +43 -0
- package/dist/transport/dispatch.d.ts.map +1 -0
- package/dist/transport/dispatch.js +91 -0
- package/dist/transport/dispatch.js.map +1 -0
- package/dist/transport/local-socket.d.ts +28 -0
- package/dist/transport/local-socket.d.ts.map +1 -0
- package/dist/transport/local-socket.js +80 -0
- package/dist/transport/local-socket.js.map +1 -0
- package/dist/transport/mock.d.ts +54 -0
- package/dist/transport/mock.d.ts.map +1 -0
- package/dist/transport/mock.js +210 -0
- package/dist/transport/mock.js.map +1 -0
- package/dist/transport/postmessage.d.ts +36 -0
- package/dist/transport/postmessage.d.ts.map +1 -0
- package/dist/transport/postmessage.js +78 -0
- package/dist/transport/postmessage.js.map +1 -0
- package/dist/transport/types.d.ts +25 -0
- package/dist/transport/types.d.ts.map +1 -0
- package/dist/transport/types.js +11 -0
- package/dist/transport/types.js.map +1 -0
- package/dist/ui/index.d.ts +107 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +32 -0
- package/dist/ui/index.js.map +1 -0
- package/package.json +84 -0
- package/src/background/index.ts +177 -0
- package/src/background/register.ts +89 -0
- package/src/dev-reload.ts +59 -0
- package/src/envelope.ts +54 -0
- package/src/globals.ts +62 -0
- package/src/hardware.ts +40 -0
- package/src/index.ts +216 -0
- package/src/modules/actions.ts +189 -0
- package/src/modules/ai.ts +281 -0
- package/src/modules/auth.ts +68 -0
- package/src/modules/base64.ts +87 -0
- package/src/modules/blob.ts +341 -0
- package/src/modules/camera.ts +258 -0
- package/src/modules/cloud.ts +94 -0
- package/src/modules/dashboard.ts +51 -0
- package/src/modules/display.ts +207 -0
- package/src/modules/events.ts +318 -0
- package/src/modules/glasses.ts +58 -0
- package/src/modules/heading.ts +23 -0
- package/src/modules/imu.ts +44 -0
- package/src/modules/input.ts +66 -0
- package/src/modules/keys.ts +46 -0
- package/src/modules/languages.ts +72 -0
- package/src/modules/led.ts +61 -0
- package/src/modules/location.ts +39 -0
- package/src/modules/memory.ts +224 -0
- package/src/modules/mic.ts +103 -0
- package/src/modules/miniapps.ts +103 -0
- package/src/modules/navigation.ts +674 -0
- package/src/modules/permissions.ts +83 -0
- package/src/modules/phone.ts +155 -0
- package/src/modules/pivots/engine.ts +863 -0
- package/src/modules/pivots/geometry.ts +409 -0
- package/src/modules/pivots/instructions.ts +341 -0
- package/src/modules/speaker.ts +347 -0
- package/src/modules/speakers.ts +40 -0
- package/src/modules/storage.ts +109 -0
- package/src/modules/stream.ts +168 -0
- package/src/modules/system.ts +113 -0
- package/src/modules/transcription.ts +180 -0
- package/src/modules/translation.ts +156 -0
- package/src/modules/ui.ts +481 -0
- package/src/protocol.ts +475 -0
- package/src/react/MiniappHeader.tsx +114 -0
- package/src/react/MiniappProvider.tsx +50 -0
- package/src/react/capsuleHeaderLayout.ts +11 -0
- package/src/react/index.ts +12 -0
- package/src/react/useCapabilities.ts +29 -0
- package/src/react/useCapsuleHeaderStyle.ts +55 -0
- package/src/react/useColorScheme.ts +26 -0
- package/src/react/useConnected.ts +47 -0
- package/src/react/useRpc.ts +103 -0
- package/src/react/useSafeArea.ts +36 -0
- package/src/session.ts +969 -0
- package/src/sub-path-types.test-d.ts +60 -0
- package/src/transport/auto.ts +143 -0
- package/src/transport/dispatch.ts +106 -0
- package/src/transport/local-socket.ts +91 -0
- package/src/transport/mock.ts +255 -0
- package/src/transport/postmessage.ts +95 -0
- package/src/transport/types.ts +26 -0
- package/src/ui/index.ts +129 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview EventManager — internal subscription registry + escape hatch.
|
|
3
|
+
*
|
|
4
|
+
* Most miniapp authors should NOT touch this directly. Use the typed methods
|
|
5
|
+
* on domain modules instead:
|
|
6
|
+
* - session.transcription.on(...)
|
|
7
|
+
* - session.mic.onAudioChunk(...) / onVoiceActivity(...)
|
|
8
|
+
* - session.input.onButtonPress(...)
|
|
9
|
+
* - session.imu.onHeadPosition(...)
|
|
10
|
+
* - session.location.onUpdate(...)
|
|
11
|
+
* - session.glasses.onBattery(...) / onConnection(...)
|
|
12
|
+
* - session.phone.notifications.on(...) / session.phone.onBattery(...)
|
|
13
|
+
*
|
|
14
|
+
* This module's only public method is `subscribe(rawStreamType, handler)` —
|
|
15
|
+
* a forward-compat escape hatch for new event types not yet wrapped on a
|
|
16
|
+
* domain module. Officially undocumented; the typed methods on domain
|
|
17
|
+
* modules are the canonical surface.
|
|
18
|
+
*
|
|
19
|
+
* Internally, EventManager owns:
|
|
20
|
+
* 1. The ref-count map. Outbound SUBSCRIBE is only sent when a stream's
|
|
21
|
+
* ref count transitions 0↔1, so multiple components listening for the
|
|
22
|
+
* same stream issue one wire-level subscribe.
|
|
23
|
+
* 2. Inbound event fan-out via `_forwardEvent(streamType, data)`, called
|
|
24
|
+
* by MiniappSession.handleIncoming when an EVENT envelope arrives.
|
|
25
|
+
*
|
|
26
|
+
* Domain modules call back into the session via session._subscribe(...) which
|
|
27
|
+
* in turn delegates to this class — the session is the integration point;
|
|
28
|
+
* domain modules don't see this class directly.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import {EventEmitter} from "eventemitter3"
|
|
32
|
+
|
|
33
|
+
import {MiniappRequestType, MiniappStreamType} from "../protocol"
|
|
34
|
+
import {MiniappSession} from "../session"
|
|
35
|
+
|
|
36
|
+
export type UnsubscribeFn = () => void
|
|
37
|
+
|
|
38
|
+
/** @internal Host-selected delivery target for a transcription event. */
|
|
39
|
+
export type TranscriptionEventRoute = "default" | "forceLocal" | "all"
|
|
40
|
+
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Shared event data shapes — re-exported by index.ts so consumers can type
|
|
43
|
+
// their handlers without importing this internal module.
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
export interface TranscriptionData {
|
|
47
|
+
text: string
|
|
48
|
+
isFinal: boolean
|
|
49
|
+
language?: string
|
|
50
|
+
/**
|
|
51
|
+
* Speaker id when the platform reports one. A stable enrolled-speaker id
|
|
52
|
+
* (`spk_…`) when on-device voice recognition resolved the voice, else
|
|
53
|
+
* the provider's anonymous diarization label (e.g. "1"), else absent.
|
|
54
|
+
* Enrolled display names are never exposed to miniapps — ids only.
|
|
55
|
+
*/
|
|
56
|
+
speakerId?: string
|
|
57
|
+
/**
|
|
58
|
+
* On-device voice recognition against the device owner's enrolled voice
|
|
59
|
+
* (final segments only). true = the user themself spoke; false =
|
|
60
|
+
* confidently a different voice; absent = no confident determination
|
|
61
|
+
* (or voice recognition is off / not enrolled).
|
|
62
|
+
*/
|
|
63
|
+
isUser?: boolean
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface TranslationData {
|
|
67
|
+
text: string
|
|
68
|
+
isFinal: boolean
|
|
69
|
+
sourceLanguage: string
|
|
70
|
+
targetLanguage: string
|
|
71
|
+
/** Source-language text of the same utterance, when the provider supplies it. */
|
|
72
|
+
originalText?: string
|
|
73
|
+
/** Stable id correlating interim + final results of one utterance. */
|
|
74
|
+
utteranceId?: string
|
|
75
|
+
/** Speaker id when the provider reports diarization. */
|
|
76
|
+
speakerId?: string
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface ButtonPressData {
|
|
80
|
+
buttonId: string
|
|
81
|
+
pressType: "short" | "long"
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface HeadPositionData {
|
|
85
|
+
position: "up" | "down"
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface AccelData {
|
|
89
|
+
/** Accelerometer axes in g (gravity-normalized). */
|
|
90
|
+
x: number
|
|
91
|
+
y: number
|
|
92
|
+
z: number
|
|
93
|
+
/** Unix ms timestamp of the reading. */
|
|
94
|
+
timestamp: number
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface LocationData {
|
|
98
|
+
lat: number
|
|
99
|
+
lng: number
|
|
100
|
+
/** Accuracy in meters, if the platform reported it. */
|
|
101
|
+
accuracy?: number
|
|
102
|
+
/** Unix ms timestamp of the fix. */
|
|
103
|
+
timestamp?: number
|
|
104
|
+
/** Set when this event is a response to a single-location request. */
|
|
105
|
+
correlationId?: string
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface HeadingData {
|
|
109
|
+
/** Compass heading in degrees, 0 = north, 90 = east. */
|
|
110
|
+
degrees: number
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface BatteryData {
|
|
114
|
+
level: number
|
|
115
|
+
charging: boolean
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface ConnectionData {
|
|
119
|
+
connected: boolean
|
|
120
|
+
modelName?: string
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Glasses Wi-Fi state. `connected` is false on glasses without Wi-Fi or when offline. */
|
|
124
|
+
export interface WifiData {
|
|
125
|
+
connected: boolean
|
|
126
|
+
ssid?: string
|
|
127
|
+
localIp?: string
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface PhoneNotificationData {
|
|
131
|
+
/** Stable id from the phone's notification listener. */
|
|
132
|
+
notificationId: string
|
|
133
|
+
/** Human app name (e.g. "Messages"). */
|
|
134
|
+
app: string
|
|
135
|
+
title: string
|
|
136
|
+
content: string
|
|
137
|
+
/** Android priority string; empty on iOS. */
|
|
138
|
+
priority: string
|
|
139
|
+
timestamp: number
|
|
140
|
+
/** Reverse-DNS package/bundle id of the originating app. */
|
|
141
|
+
packageName: string
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface NotificationDismissedData {
|
|
145
|
+
/** Same id as the matching post event from `notifications.on(...)`. */
|
|
146
|
+
notificationId: string
|
|
147
|
+
/** Android NotificationKey. More stable than notificationId across reposts. */
|
|
148
|
+
notificationKey?: string
|
|
149
|
+
/** Reverse-DNS package/bundle id of the originating app. */
|
|
150
|
+
packageName?: string
|
|
151
|
+
/** Unix ms timestamp of the dismissal. */
|
|
152
|
+
timestamp: number
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface VadData {
|
|
156
|
+
/** True while the user is speaking (voice detected), false when silent. */
|
|
157
|
+
status: boolean
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export interface TouchData {
|
|
161
|
+
/**
|
|
162
|
+
* The gesture: single_tap, double_tap, triple_tap, long_press, swipe_up, or
|
|
163
|
+
* swipe_down — plus the raw press edges tap_start (finger down) and tap_end
|
|
164
|
+
* (finger up), which bracket every gesture on hardware that reports them.
|
|
165
|
+
*/
|
|
166
|
+
kind:
|
|
167
|
+
| "single_tap"
|
|
168
|
+
| "double_tap"
|
|
169
|
+
| "triple_tap"
|
|
170
|
+
| "long_press"
|
|
171
|
+
| "swipe_up"
|
|
172
|
+
| "swipe_down"
|
|
173
|
+
| "tap_start"
|
|
174
|
+
| "tap_end"
|
|
175
|
+
| string
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export interface AudioChunkData {
|
|
179
|
+
/** PCM or LC3, base64-encoded. Format depends on phone's mic mode. */
|
|
180
|
+
data: string
|
|
181
|
+
sampleRate?: number
|
|
182
|
+
format?: string
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
// Registry
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
|
|
189
|
+
export class EventManager {
|
|
190
|
+
private readonly emitter = new EventEmitter()
|
|
191
|
+
/** Stream -> ref count. Outbound SUBSCRIBE tracks active streams and routing changes. */
|
|
192
|
+
private readonly refCounts = new Map<string, number>()
|
|
193
|
+
/** Transcription stream -> refs that require on-device-only processing. */
|
|
194
|
+
private readonly forceLocalRefCounts = new Map<string, number>()
|
|
195
|
+
|
|
196
|
+
constructor(private readonly session: MiniappSession) {}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Generic subscribe. `stream` is the raw wire value, including any language
|
|
200
|
+
* suffix like `"transcription:en-US"`. Forward-compat escape hatch for new
|
|
201
|
+
* event types not yet wrapped on a domain module. Most authors should use
|
|
202
|
+
* typed methods on domain modules instead.
|
|
203
|
+
*
|
|
204
|
+
* Stream names beginning with `_` (e.g. `_ui` for the session.ui bus) are
|
|
205
|
+
* INTERNAL — they ride the local _forwardEvent fan-out and never appear in
|
|
206
|
+
* the outbound SUBSCRIBE list. Native already knows to deliver UI envelopes
|
|
207
|
+
* via a separate envelope `type`; the EventManager only routes them
|
|
208
|
+
* locally.
|
|
209
|
+
*/
|
|
210
|
+
subscribe(stream: string, handler: (data: unknown) => void, options: {forceLocal?: boolean} = {}): UnsubscribeFn {
|
|
211
|
+
const isInternal = stream.startsWith("_")
|
|
212
|
+
const forceLocal = options.forceLocal === true && stream.startsWith(`${MiniappStreamType.TRANSCRIPTION}:`)
|
|
213
|
+
const listener = (data: unknown, route?: TranscriptionEventRoute) => {
|
|
214
|
+
if (stream.startsWith(`${MiniappStreamType.TRANSCRIPTION}:`)) {
|
|
215
|
+
// Hosts that predate routed transcription events omit `route`. Treat
|
|
216
|
+
// those events as the default/cloud path so a forceLocal listener can
|
|
217
|
+
// never accidentally receive cloud text.
|
|
218
|
+
if (forceLocal ? route !== "forceLocal" && route !== "all" : route === "forceLocal") return
|
|
219
|
+
}
|
|
220
|
+
handler(data)
|
|
221
|
+
}
|
|
222
|
+
this.emitter.on(stream, listener)
|
|
223
|
+
if (!isInternal) {
|
|
224
|
+
const before = this.refCounts.get(stream) ?? 0
|
|
225
|
+
this.refCounts.set(stream, before + 1)
|
|
226
|
+
const forceLocalBefore = this.forceLocalRefCounts.get(stream) ?? 0
|
|
227
|
+
const defaultBefore = before - forceLocalBefore
|
|
228
|
+
if (forceLocal) this.forceLocalRefCounts.set(stream, forceLocalBefore + 1)
|
|
229
|
+
if (before === 0 || (forceLocal && forceLocalBefore === 0) || (!forceLocal && defaultBefore === 0)) {
|
|
230
|
+
this.sendSubscriptionUpdate()
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return () => {
|
|
234
|
+
this.emitter.off(stream, listener)
|
|
235
|
+
if (isInternal) return
|
|
236
|
+
const current = this.refCounts.get(stream) ?? 0
|
|
237
|
+
const forceLocalCurrent = this.forceLocalRefCounts.get(stream) ?? 0
|
|
238
|
+
let subscriptionChanged = current <= 1
|
|
239
|
+
if (current <= 1) {
|
|
240
|
+
this.refCounts.delete(stream)
|
|
241
|
+
} else {
|
|
242
|
+
this.refCounts.set(stream, current - 1)
|
|
243
|
+
}
|
|
244
|
+
if (forceLocal) {
|
|
245
|
+
if (forceLocalCurrent <= 1) {
|
|
246
|
+
this.forceLocalRefCounts.delete(stream)
|
|
247
|
+
subscriptionChanged = true
|
|
248
|
+
} else {
|
|
249
|
+
this.forceLocalRefCounts.set(stream, forceLocalCurrent - 1)
|
|
250
|
+
}
|
|
251
|
+
} else if (current - forceLocalCurrent <= 1) {
|
|
252
|
+
subscriptionChanged = true
|
|
253
|
+
}
|
|
254
|
+
if (subscriptionChanged) this.sendSubscriptionUpdate()
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** Unsubscribe every handler on every stream this EventManager owns. */
|
|
259
|
+
unsubscribeAll(): void {
|
|
260
|
+
this.emitter.removeAllListeners()
|
|
261
|
+
this.refCounts.clear()
|
|
262
|
+
this.forceLocalRefCounts.clear()
|
|
263
|
+
this.sendSubscriptionUpdate()
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// -------------------------------------------------------------------------
|
|
267
|
+
// Internal — called by MiniappSession when EVENT arrives from phone
|
|
268
|
+
// -------------------------------------------------------------------------
|
|
269
|
+
|
|
270
|
+
/** @internal */
|
|
271
|
+
_forwardEvent(stream: string, data: unknown, transcriptionRoute?: TranscriptionEventRoute): void {
|
|
272
|
+
this.emitter.emit(stream, data, transcriptionRoute)
|
|
273
|
+
|
|
274
|
+
// Wildcard fan-out: handlers register under wildcard patterns
|
|
275
|
+
// ("transcription:auto", "translation:*:<target>", …) but the host
|
|
276
|
+
// delivers events under the CONCRETE stream key
|
|
277
|
+
// ("transcription:en-US", "translation:en:es"). Re-emit on every
|
|
278
|
+
// pattern the concrete key satisfies — the same pattern set
|
|
279
|
+
// LocalMiniappRuntime.forwardEvent matches on the host side —
|
|
280
|
+
// otherwise a wildcard subscriber never fires.
|
|
281
|
+
if (stream.startsWith("transcription:") && stream !== "transcription:auto") {
|
|
282
|
+
this.emitter.emit("transcription:auto", data, transcriptionRoute)
|
|
283
|
+
} else if (stream.startsWith("translation:")) {
|
|
284
|
+
const parts = stream.split(":")
|
|
285
|
+
const patterns = new Set<string>(["translation:auto"])
|
|
286
|
+
if (parts.length === 3) {
|
|
287
|
+
const [, source, target] = parts
|
|
288
|
+
patterns.add("translation:*:*")
|
|
289
|
+
patterns.add(`translation:*:${target}`)
|
|
290
|
+
patterns.add(`translation:${source}:*`)
|
|
291
|
+
}
|
|
292
|
+
patterns.delete(stream) // exact key already emitted above
|
|
293
|
+
for (const pattern of patterns) {
|
|
294
|
+
this.emitter.emit(pattern, data)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
private sendSubscriptionUpdate(): void {
|
|
300
|
+
const subscriptions = Array.from(this.refCounts.keys()).map((stream) => {
|
|
301
|
+
if (stream === MiniappStreamType.LOCATION_UPDATE) return {stream: "location_stream", rate: "realtime"}
|
|
302
|
+
|
|
303
|
+
const forceLocalRefs = this.forceLocalRefCounts.get(stream) ?? 0
|
|
304
|
+
if (forceLocalRefs === 0) return stream
|
|
305
|
+
|
|
306
|
+
const totalRefs = this.refCounts.get(stream) ?? 0
|
|
307
|
+
return {
|
|
308
|
+
stream,
|
|
309
|
+
forceLocal: true,
|
|
310
|
+
...(totalRefs > forceLocalRefs ? {includeCloud: true} : {}),
|
|
311
|
+
}
|
|
312
|
+
})
|
|
313
|
+
this.session.sendOneShot({
|
|
314
|
+
type: MiniappRequestType.SUBSCRIBE,
|
|
315
|
+
subscriptions,
|
|
316
|
+
})
|
|
317
|
+
}
|
|
318
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview GlassesModule — device-state events for the glasses themselves.
|
|
3
|
+
*
|
|
4
|
+
* Reports on the glasses hardware: battery level + charging state, connection
|
|
5
|
+
* status (connected/disconnected, model name). The phone has its own battery
|
|
6
|
+
* + connection events on `session.phone`.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {MiniappRequestType, MiniappStreamType} from "../protocol"
|
|
10
|
+
import {MiniappSession} from "../session"
|
|
11
|
+
import type {BatteryData, ConnectionData, UnsubscribeFn, WifiData} from "./events"
|
|
12
|
+
|
|
13
|
+
export class GlassesModule {
|
|
14
|
+
constructor(private readonly session: MiniappSession) {}
|
|
15
|
+
|
|
16
|
+
onBattery(handler: (data: BatteryData) => void): UnsubscribeFn {
|
|
17
|
+
return this.session._subscribe(MiniappStreamType.GLASSES_BATTERY, handler as (data: unknown) => void)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
onConnection(handler: (data: ConnectionData) => void): UnsubscribeFn {
|
|
21
|
+
return this.session._subscribe(MiniappStreamType.GLASSES_CONNECTION, handler as (data: unknown) => void)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Subscribe to glasses Wi-Fi state. Fires the current state on subscribe and
|
|
26
|
+
* on every change. `connected` is false on glasses without Wi-Fi. Streaming
|
|
27
|
+
* requires Wi-Fi — pair this with {@link requestWifiSetup} to prompt the user.
|
|
28
|
+
*/
|
|
29
|
+
onWifi(handler: (data: WifiData) => void): UnsubscribeFn {
|
|
30
|
+
return this.session._subscribe(MiniappStreamType.GLASSES_WIFI, handler as (data: unknown) => void)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Ask the host to open the glasses Wi-Fi setup flow on the phone. Mirrors the
|
|
35
|
+
* cloud SDK's `session.requestWifiSetup(reason)`. `reason` is a user-facing
|
|
36
|
+
* line shown in the setup prompt (e.g. "Streaming needs your glasses on Wi-Fi").
|
|
37
|
+
*/
|
|
38
|
+
async requestWifiSetup(reason?: string): Promise<void> {
|
|
39
|
+
await this.session.sendRequest<void>({
|
|
40
|
+
type: MiniappRequestType.REQUEST_WIFI_SETUP,
|
|
41
|
+
reason,
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Enable or disable Wi-Fi ADB (wireless debugging) on Mentra Live.
|
|
47
|
+
* Persisted on the glasses; boot applies the saved preference (default off).
|
|
48
|
+
*
|
|
49
|
+
* SYSTEM-only — rejects with `NOT_PERMITTED` unless this miniapp is a system
|
|
50
|
+
* app. Third-party miniapps must not expose wireless debugging controls.
|
|
51
|
+
*/
|
|
52
|
+
async setWifiAdbState(enabled: boolean): Promise<void> {
|
|
53
|
+
await this.session.sendRequest<void>({
|
|
54
|
+
type: MiniappRequestType.SET_WIFI_ADB_STATE,
|
|
55
|
+
enabled,
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview HeadingModule — phone compass heading events.
|
|
3
|
+
*
|
|
4
|
+
* Works on both Android and iOS. LOCATION permission must be declared in miniapp.json.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {MiniappStreamType} from "../protocol"
|
|
8
|
+
import {MiniappSession} from "../session"
|
|
9
|
+
import type {HeadingData, UnsubscribeFn} from "./events"
|
|
10
|
+
|
|
11
|
+
export class HeadingModule {
|
|
12
|
+
constructor(private readonly session: MiniappSession) {}
|
|
13
|
+
|
|
14
|
+
/** True iff `LOCATION` is declared in the miniapp's manifest. */
|
|
15
|
+
get hasPermission(): boolean {
|
|
16
|
+
return this.session._hasManifestPermission("LOCATION")
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Subscribe to continuous compass heading updates. */
|
|
20
|
+
onUpdate(handler: (data: HeadingData) => void): UnsubscribeFn {
|
|
21
|
+
return this.session._subscribe(MiniappStreamType.HEADING_UPDATE, handler as (data: unknown) => void)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview ImuModule — head position + motion events.
|
|
3
|
+
*
|
|
4
|
+
* Glasses' inertial measurement unit. Exposes head-up/down position and a
|
|
5
|
+
* raw accelerometer stream (`onAccel`). Gyroscope / magnetometer / fused
|
|
6
|
+
* orientation are future work — when added they'll surface as a combined IMU
|
|
7
|
+
* event rather than extending this single-sensor accel stream.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {MiniappRequestType, MiniappStreamType} from "../protocol"
|
|
11
|
+
import {MiniappSession} from "../session"
|
|
12
|
+
import type {AccelData, HeadPositionData, UnsubscribeFn} from "./events"
|
|
13
|
+
|
|
14
|
+
export class ImuModule {
|
|
15
|
+
constructor(private readonly session: MiniappSession) {}
|
|
16
|
+
|
|
17
|
+
onHeadPosition(handler: (data: HeadPositionData) => void): UnsubscribeFn {
|
|
18
|
+
return this.session._subscribe(MiniappStreamType.HEAD_POSITION, handler as (data: unknown) => void)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Subscribe to raw accelerometer readings (`{x, y, z}` in g) from the
|
|
23
|
+
* glasses IMU. G2 only today; on glasses without an exposed IMU stream the
|
|
24
|
+
* subscription succeeds but no events fire.
|
|
25
|
+
*/
|
|
26
|
+
onAccel(handler: (data: AccelData) => void): UnsubscribeFn {
|
|
27
|
+
return this.session._subscribe(MiniappStreamType.ACCEL_DATA, handler as (data: unknown) => void)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Explicitly enable or disable raw accelerometer streaming on the glasses.
|
|
32
|
+
*
|
|
33
|
+
* Subscribing via `onAccel` already auto-enables the sensor and unsubscribing
|
|
34
|
+
* disables it, so most callers don't need this. Use it when you want to drive
|
|
35
|
+
* the IMU directly — e.g. a diagnostic toggle. G2 only today; a no-op on
|
|
36
|
+
* glasses without an exposed IMU stream.
|
|
37
|
+
*/
|
|
38
|
+
setEnabled(enabled: boolean): Promise<void> {
|
|
39
|
+
return this.session.sendRequest<void>({
|
|
40
|
+
type: MiniappRequestType.IMU_SET_ENABLED,
|
|
41
|
+
enabled,
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview InputModule — physical control events on the glasses.
|
|
3
|
+
*
|
|
4
|
+
* Combines button + touch surfaces. Future input modes (gesture, voice
|
|
5
|
+
* command, eye tracking) will extend this module rather than spawning new
|
|
6
|
+
* top-level modules.
|
|
7
|
+
*
|
|
8
|
+
* Touch overloads:
|
|
9
|
+
*
|
|
10
|
+
* session.input.onTouch(handler)
|
|
11
|
+
* session.input.onTouch("single_tap", handler)
|
|
12
|
+
* session.input.onTouch(["swipe_up", "swipe_down"], handler)
|
|
13
|
+
*
|
|
14
|
+
* Per-gesture filtering rides on `touch_event:<gesture>` stream variants
|
|
15
|
+
* the phone runtime fans out alongside the bare `touch_event` stream.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import {MiniappStreamType} from "../protocol"
|
|
19
|
+
import {MiniappSession} from "../session"
|
|
20
|
+
import type {ButtonPressData, TouchData, UnsubscribeFn} from "./events"
|
|
21
|
+
|
|
22
|
+
export class InputModule {
|
|
23
|
+
constructor(private readonly session: MiniappSession) {}
|
|
24
|
+
|
|
25
|
+
onButtonPress(handler: (data: ButtonPressData) => void): UnsubscribeFn {
|
|
26
|
+
return this.session._subscribe(MiniappStreamType.BUTTON_PRESS, handler as (data: unknown) => void)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Subscribe to touch events.
|
|
31
|
+
*
|
|
32
|
+
* onTouch(handler) — all touch events
|
|
33
|
+
* onTouch("single_tap", handler) — only single taps
|
|
34
|
+
* onTouch(["a","b"], handler) — multiple gestures, single subscription
|
|
35
|
+
*/
|
|
36
|
+
onTouch(handler: (data: TouchData) => void): UnsubscribeFn
|
|
37
|
+
onTouch(gesture: string, handler: (data: TouchData) => void): UnsubscribeFn
|
|
38
|
+
onTouch(gestures: string[], handler: (data: TouchData) => void): UnsubscribeFn
|
|
39
|
+
onTouch(
|
|
40
|
+
gestureOrHandler: string | string[] | ((data: TouchData) => void),
|
|
41
|
+
maybeHandler?: (data: TouchData) => void,
|
|
42
|
+
): UnsubscribeFn {
|
|
43
|
+
// Plain handler — subscribe to all touches.
|
|
44
|
+
if (typeof gestureOrHandler === "function") {
|
|
45
|
+
return this.session._subscribe(MiniappStreamType.TOUCH_EVENT, gestureOrHandler as (data: unknown) => void)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const handler = maybeHandler!
|
|
49
|
+
const gestures = Array.isArray(gestureOrHandler) ? gestureOrHandler : [gestureOrHandler]
|
|
50
|
+
if (gestures.length === 0) return () => {}
|
|
51
|
+
|
|
52
|
+
const unsubs: UnsubscribeFn[] = []
|
|
53
|
+
for (const g of gestures) {
|
|
54
|
+
unsubs.push(this.session._subscribe(`${MiniappStreamType.TOUCH_EVENT}:${g}`, handler as (data: unknown) => void))
|
|
55
|
+
}
|
|
56
|
+
return () => {
|
|
57
|
+
for (const u of unsubs) {
|
|
58
|
+
try {
|
|
59
|
+
u()
|
|
60
|
+
} catch {
|
|
61
|
+
/* ignore */
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview KeysModule — read user-supplied provider API keys.
|
|
3
|
+
*
|
|
4
|
+
* Users can enter their own provider API keys in the Glassly App
|
|
5
|
+
* (Settings > API Keys). A miniapp that declares the API_KEYS permission in
|
|
6
|
+
* miniapp.json can read the providers in {@link ApiKeyProvider}; the phone
|
|
7
|
+
* runtime rejects with PERMISSION_NOT_DECLARED otherwise.
|
|
8
|
+
*
|
|
9
|
+
* The provider set is an explicit host-side allowlist — asking for anything
|
|
10
|
+
* else (e.g. the Soniox transcription key, which is deliberately not exposed
|
|
11
|
+
* to miniapps) rejects with INVALID_ARGUMENT. Keys are device-local settings:
|
|
12
|
+
* the value travels only across the local bridge, never through any server.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {MiniappRequestType} from "../protocol"
|
|
16
|
+
import {MiniappSession} from "../session"
|
|
17
|
+
|
|
18
|
+
/** Providers whose user-supplied keys miniapps may read. */
|
|
19
|
+
export type ApiKeyProvider = "anthropic" | "gemini" | "openai" | "elevenlabs" | "mapbox"
|
|
20
|
+
|
|
21
|
+
interface KeysGetResult {
|
|
22
|
+
key: string | null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class KeysModule {
|
|
26
|
+
constructor(private readonly session: MiniappSession) {}
|
|
27
|
+
|
|
28
|
+
/** True iff `API_KEYS` is declared in the miniapp's manifest. */
|
|
29
|
+
get hasPermission(): boolean {
|
|
30
|
+
return this.session._hasManifestPermission("API_KEYS")
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Fetch the user's API key for a provider. Resolves with the key, or null
|
|
35
|
+
* when the user hasn't saved one for that provider. Values are not cached —
|
|
36
|
+
* re-fetch (e.g. on retry after a 401 from the provider) to pick up a key
|
|
37
|
+
* the user edited in Settings while the miniapp was running.
|
|
38
|
+
*/
|
|
39
|
+
async get(provider: ApiKeyProvider): Promise<string | null> {
|
|
40
|
+
const result = await this.session.sendRequest<KeysGetResult>({
|
|
41
|
+
type: MiniappRequestType.KEYS_GET,
|
|
42
|
+
provider,
|
|
43
|
+
})
|
|
44
|
+
return result?.key ?? null
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Language validation at the SDK boundary (issue 021, WP2).
|
|
3
|
+
*
|
|
4
|
+
* The registry itself lives in `@glassly/cloud-protocol/languages` (single
|
|
5
|
+
* source of truth shared with the cloud). This module re-exports the types so
|
|
6
|
+
* miniapps import everything from `@glassly/miniapp`, and adds the throwing
|
|
7
|
+
* validators the typed module surfaces (`transcription.*`, `translation.*`)
|
|
8
|
+
* call on every language parameter.
|
|
9
|
+
*
|
|
10
|
+
* Contract (design doc docs/issues/021-typed-language-subscriptions):
|
|
11
|
+
* - Valid by construction: params are typed as literal unions, so TS users
|
|
12
|
+
* get autocomplete and compile errors.
|
|
13
|
+
* - Loud at runtime: JS users (or `any` casts) get a thrown
|
|
14
|
+
* `MiniappValidationError` with a suggestion, never silent fallback.
|
|
15
|
+
* - Canonical on the wire: bare codes ("fr") canonicalize to their default
|
|
16
|
+
* tag ("fr-FR") before becoming stream keys, so the phone/cloud only ever
|
|
17
|
+
* see registry tags.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
isTranscriptionLanguage,
|
|
22
|
+
suggestTranscriptionLanguage,
|
|
23
|
+
toLanguageHint,
|
|
24
|
+
toTranscriptionLanguage,
|
|
25
|
+
SUPPORTED_LANGUAGE_HINTS,
|
|
26
|
+
SUPPORTED_TRANSCRIPTION_LANGUAGES,
|
|
27
|
+
} from "@glassly/cloud-protocol/languages"
|
|
28
|
+
import type {LanguageHint, TranscriptionLanguage} from "@glassly/cloud-protocol/languages"
|
|
29
|
+
|
|
30
|
+
export {SUPPORTED_LANGUAGE_HINTS, SUPPORTED_TRANSCRIPTION_LANGUAGES, isTranscriptionLanguage}
|
|
31
|
+
export type {LanguageHint, TranscriptionLanguage}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Thrown synchronously by SDK methods when a parameter fails validation.
|
|
35
|
+
* Deliberately a hard throw (not a warning, not a silent default): an invalid
|
|
36
|
+
* language previously produced a subscription that silently transcribed the
|
|
37
|
+
* wrong language or silently never spoke (OS-1746).
|
|
38
|
+
*/
|
|
39
|
+
export class MiniappValidationError extends Error {
|
|
40
|
+
constructor(message: string) {
|
|
41
|
+
super(message)
|
|
42
|
+
this.name = "MiniappValidationError"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Canonicalize a subscription language or throw. Accepts registry tags
|
|
48
|
+
* ("fr-FR") and bare registry codes ("fr" -> "fr-FR").
|
|
49
|
+
*/
|
|
50
|
+
export function requireTranscriptionLanguage(value: string, param: string): TranscriptionLanguage {
|
|
51
|
+
const canonical = toTranscriptionLanguage(value)
|
|
52
|
+
if (canonical) return canonical
|
|
53
|
+
const suggestion = suggestTranscriptionLanguage(value)
|
|
54
|
+
throw new MiniappValidationError(
|
|
55
|
+
`${param}: unknown language ${JSON.stringify(value)}` +
|
|
56
|
+
(suggestion ? ` — did you mean ${JSON.stringify(suggestion)}?` : "") +
|
|
57
|
+
` Supported values are BCP-47 tags from SUPPORTED_TRANSCRIPTION_LANGUAGES (e.g. "en-US", "fr-FR").`,
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Normalize a language hint to the bare code the STT provider accepts, or
|
|
63
|
+
* throw. Accepts bare codes ("fr") and registry tags ("fr-FR" -> "fr").
|
|
64
|
+
*/
|
|
65
|
+
export function requireLanguageHint(value: string, param: string): LanguageHint {
|
|
66
|
+
const hint = toLanguageHint(value)
|
|
67
|
+
if (hint) return hint
|
|
68
|
+
throw new MiniappValidationError(
|
|
69
|
+
`${param}: unknown language hint ${JSON.stringify(value)}. ` +
|
|
70
|
+
`Hints are bare ISO 639-1 codes from SUPPORTED_LANGUAGE_HINTS (e.g. "en", "fr").`,
|
|
71
|
+
)
|
|
72
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview LedModule — glasses RGB LED control.
|
|
3
|
+
*
|
|
4
|
+
* API mirrors the cloud SDK's LED module. Colors are named strings
|
|
5
|
+
* (the phone maps them to per-device LED indices). Actions are "on" / "off".
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {MiniappRequestType} from "../protocol"
|
|
9
|
+
import {MiniappSession} from "../session"
|
|
10
|
+
|
|
11
|
+
export type LedColor = "red" | "green" | "blue" | "orange" | "white"
|
|
12
|
+
|
|
13
|
+
export type LedControlResult = {
|
|
14
|
+
type?: "rgb_led_control_response"
|
|
15
|
+
state: "success"
|
|
16
|
+
requestId: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface LedControlOptions {
|
|
20
|
+
color?: LedColor
|
|
21
|
+
/** LED on duration in ms. */
|
|
22
|
+
ontime?: number
|
|
23
|
+
/** LED off duration in ms. */
|
|
24
|
+
offtime?: number
|
|
25
|
+
/** Number of on/off cycles. */
|
|
26
|
+
count?: number
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class LedModule {
|
|
30
|
+
constructor(private readonly session: MiniappSession) {}
|
|
31
|
+
|
|
32
|
+
/** Turn an LED on with the given pattern. Resolves after the glasses acknowledge the command. */
|
|
33
|
+
async turnOn(options: LedControlOptions = {}): Promise<LedControlResult> {
|
|
34
|
+
return this.session.sendRequest<LedControlResult>({
|
|
35
|
+
type: MiniappRequestType.RGB_LED,
|
|
36
|
+
action: "on",
|
|
37
|
+
color: options.color ?? "red",
|
|
38
|
+
ontime: options.ontime ?? 1000,
|
|
39
|
+
offtime: options.offtime ?? 0,
|
|
40
|
+
count: options.count ?? 1,
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Turn all LEDs off. Resolves after the glasses acknowledge the command. */
|
|
45
|
+
async turnOff(): Promise<LedControlResult> {
|
|
46
|
+
return this.session.sendRequest<LedControlResult>({
|
|
47
|
+
type: MiniappRequestType.RGB_LED,
|
|
48
|
+
action: "off",
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Blink pattern — repeats `count` times with ontime/offtime. */
|
|
53
|
+
async blink(color: LedColor, ontime: number, offtime: number, count: number): Promise<LedControlResult> {
|
|
54
|
+
return this.turnOn({color, ontime, offtime, count})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Solid LED for a fixed duration. */
|
|
58
|
+
async solid(color: LedColor, duration: number): Promise<LedControlResult> {
|
|
59
|
+
return this.turnOn({color, ontime: duration, offtime: 0, count: 1})
|
|
60
|
+
}
|
|
61
|
+
}
|