@glassly/miniapp 0.1.0-dev.26
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 +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 +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 +174 -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 +50 -0
- package/dist/modules/input.d.ts.map +1 -0
- package/dist/modules/input.js +70 -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 +390 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +419 -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 +88 -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 +25 -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 +214 -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 +87 -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 +482 -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,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview DashboardAPI. setContent() is a noop in v1 (the cloud
|
|
3
|
+
* DashboardManager owns widget rendering); showNativeDashboard() is live.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {MiniappRequestType} from "../protocol"
|
|
7
|
+
import {MiniappSession} from "../session"
|
|
8
|
+
|
|
9
|
+
export type DashboardMode = "main" | "expanded" | "always_on"
|
|
10
|
+
|
|
11
|
+
/** "shown" = forwarded to the glasses; "unsupported" = the host refused (e.g. native nav mode owns the screen). */
|
|
12
|
+
export interface ShowNativeDashboardResult {
|
|
13
|
+
status: "shown" | "unsupported"
|
|
14
|
+
reason?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class DashboardAPI {
|
|
18
|
+
private warned = false
|
|
19
|
+
|
|
20
|
+
constructor(private readonly session: MiniappSession) {}
|
|
21
|
+
|
|
22
|
+
setContent(mode: DashboardMode, content: string): void {
|
|
23
|
+
if (!this.warned) {
|
|
24
|
+
console.warn("[@glassly/miniapp] dashboard.setContent() is deferred in v1.")
|
|
25
|
+
this.warned = true
|
|
26
|
+
}
|
|
27
|
+
// Still forward so the phone can log/ignore consistently.
|
|
28
|
+
this.session.sendOneShot({
|
|
29
|
+
type: MiniappRequestType.DASHBOARD_CONTENT_UPDATE,
|
|
30
|
+
mode,
|
|
31
|
+
content,
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Bring the glasses' native dashboard (the OS-level idle screen) to the
|
|
37
|
+
* foreground. On the G2 this tears down the current page; the display
|
|
38
|
+
* returns when the wearer dismisses the dashboard or content is re-rendered.
|
|
39
|
+
*/
|
|
40
|
+
showNativeDashboard(): Promise<ShowNativeDashboardResult> {
|
|
41
|
+
return this.session
|
|
42
|
+
.sendRequest<ShowNativeDashboardResult>({type: MiniappRequestType.SHOW_NATIVE_DASHBOARD})
|
|
43
|
+
.catch((err) => ({
|
|
44
|
+
status: "unsupported" as const,
|
|
45
|
+
reason:
|
|
46
|
+
typeof err === "object" && err !== null && "message" in err
|
|
47
|
+
? String((err as {message: unknown}).message)
|
|
48
|
+
: String(err),
|
|
49
|
+
}))
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview DisplayManager — the glasses display, via `render()`.
|
|
3
|
+
*
|
|
4
|
+
* render(elements) is replace-the-frame scene rendering: each call describes
|
|
5
|
+
* everything that should be on screen, the host diffs it against the previous
|
|
6
|
+
* frame per device, and `render([])` clears. The legacy one-shot `show*`
|
|
7
|
+
* layout methods were removed once nothing first-party or known-external
|
|
8
|
+
* called them; hosts still accept the historical `miniapp_display` wire shape
|
|
9
|
+
* from bundles packed with older SDKs.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {MiniappRequestType} from "../protocol"
|
|
13
|
+
import {MiniappSession} from "../session"
|
|
14
|
+
|
|
15
|
+
export type ViewType = "main" | "dashboard"
|
|
16
|
+
|
|
17
|
+
export type DisplayBreakMode = "character" | "character-no-hyphen" | "word" | "strict-word"
|
|
18
|
+
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// render() — the scene API
|
|
21
|
+
// ============================================================================
|
|
22
|
+
|
|
23
|
+
/** Canvas corner a box's `x`/`y` are measured from (insets grow inward). */
|
|
24
|
+
export type RenderAnchor = "top-left" | "top-right" | "bottom-left" | "bottom-right"
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Pixel-space bounding box on the device's drawable canvas (see
|
|
28
|
+
* `capabilities.display`).
|
|
29
|
+
*
|
|
30
|
+
* With an `anchor`, `x`/`y` become insets from that corner, growing inward,
|
|
31
|
+
* and the host resolves the box against the connected device's real canvas —
|
|
32
|
+
* e.g. `{x: 0, y: 0, w: 120, h: 120, anchor: "bottom-right"}` sits flush in
|
|
33
|
+
* the bottom-right corner on every device, whatever its resolution. Corners
|
|
34
|
+
* only; there is no center/edge anchoring or scaling. Hosts older than the
|
|
35
|
+
* anchor feature ignore the field and place the box from the top-left.
|
|
36
|
+
*/
|
|
37
|
+
export interface RenderBox {
|
|
38
|
+
x: number
|
|
39
|
+
y: number
|
|
40
|
+
w: number
|
|
41
|
+
h: number
|
|
42
|
+
/** Which canvas corner x/y are measured from. Default "top-left". */
|
|
43
|
+
anchor?: RenderAnchor
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface RenderTextStyle {
|
|
47
|
+
/** Border width in px (0/absent = none). */
|
|
48
|
+
border?: number
|
|
49
|
+
/** Border corner radius in px. */
|
|
50
|
+
radius?: number
|
|
51
|
+
/** What happens to text that doesn't fit the box after wrapping. Default "clip". */
|
|
52
|
+
overflow?: "clip" | "ellipsis"
|
|
53
|
+
/** Line-break policy for wrapping (host wraps; the box carries pre-wrapped text). */
|
|
54
|
+
breakMode?: DisplayBreakMode
|
|
55
|
+
/**
|
|
56
|
+
* "mono": fixed-pitch text drawn at `fontSize` px with a fixed line height
|
|
57
|
+
* of 1.2 × size, and NOT re-wrapped by the host — lines are taken as given.
|
|
58
|
+
* For grids (terminals, tables). Devices without a phone-side rasterizer
|
|
59
|
+
* (stock G2 firmware) fall back to their own proportional font.
|
|
60
|
+
*
|
|
61
|
+
* "builtin": the glasses draw the text themselves with their built-in 20 px
|
|
62
|
+
* proportional font (Glassly CFW G2 only — tiny updates instead of bitmap
|
|
63
|
+
* re-sends). Lines are taken as given (split on \n, never re-wrapped) and
|
|
64
|
+
* clipped at the display edge, and the render window is pinned to the
|
|
65
|
+
* middle depth level while such text is on screen. Other devices and stock
|
|
66
|
+
* firmware fall back to the default proportional face.
|
|
67
|
+
*/
|
|
68
|
+
font?: "default" | "mono" | "builtin"
|
|
69
|
+
/** Font size in px for `font: "mono"`. Default 20. */
|
|
70
|
+
fontSize?: number
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface RenderRectStyle {
|
|
74
|
+
border?: number
|
|
75
|
+
radius?: number
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* One element of a rendered scene. `id` is optional but recommended for
|
|
80
|
+
* anything that updates over time: elements with a stable id update in place on
|
|
81
|
+
* the glasses (no flicker); the host matches unnamed elements by geometry.
|
|
82
|
+
*/
|
|
83
|
+
export type RenderElement =
|
|
84
|
+
| {type: "text"; id?: string; box: RenderBox; text: string; style?: RenderTextStyle}
|
|
85
|
+
| {type: "image"; id?: string; box: RenderBox; data: string}
|
|
86
|
+
| {type: "rect"; id?: string; box: RenderBox; style?: RenderRectStyle}
|
|
87
|
+
|
|
88
|
+
export interface RenderOptions {
|
|
89
|
+
view?: ViewType
|
|
90
|
+
/** Auto-clear after this many ms (same semantics as legacy display options). */
|
|
91
|
+
durationMs?: number
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ============================================================================
|
|
95
|
+
// Native navigation UI (capability `hasNativeNavigation`, e.g. G2)
|
|
96
|
+
// ============================================================================
|
|
97
|
+
|
|
98
|
+
/** Turn-by-turn card fields — pre-formatted display strings, rendered verbatim by the device. */
|
|
99
|
+
export interface NativeNavInfo {
|
|
100
|
+
/** Firmware direction-icon index, 0…35. */
|
|
101
|
+
directionIcon: number
|
|
102
|
+
/** Distance to the next maneuver, e.g. "500 m". */
|
|
103
|
+
distance: string
|
|
104
|
+
roadName: string
|
|
105
|
+
/** Remaining trip duration, e.g. "11 min". */
|
|
106
|
+
spendTime: string
|
|
107
|
+
/** Distance to the destination, e.g. "0.9 km". */
|
|
108
|
+
remainDistance: string
|
|
109
|
+
/** Arrival time, e.g. "18:18". */
|
|
110
|
+
etaTime: string
|
|
111
|
+
/** Current speed, e.g. "5 km/h". */
|
|
112
|
+
speed: string
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* "sent" = forwarded to the device. "unsupported" = no native nav UI on this
|
|
117
|
+
* device / no active trip — callers should fall back to render().
|
|
118
|
+
*/
|
|
119
|
+
export interface NativeNavResult {
|
|
120
|
+
status: "sent" | "unsupported" | "blocked"
|
|
121
|
+
reason?: string
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Outcome of a render request, resolved when the host's display arbitration
|
|
126
|
+
* settles it. "displayed" = accepted and sent to the device — NOT a
|
|
127
|
+
* render confirmation. "blocked" = another app owns the display (or the
|
|
128
|
+
* request failed); `reason` says why.
|
|
129
|
+
*/
|
|
130
|
+
export interface RenderResult {
|
|
131
|
+
status: "displayed" | "blocked"
|
|
132
|
+
/** True when the host adjusted the scene (clamped boxes, dropped elements, degraded for the device). */
|
|
133
|
+
degraded?: boolean
|
|
134
|
+
/** Ids of elements the host dropped (budget/bounds/device limits) — dropped is never silent. */
|
|
135
|
+
dropped?: string[]
|
|
136
|
+
reason?: string
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export class DisplayManager {
|
|
140
|
+
constructor(private readonly session: MiniappSession) {}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Render a whole scene of positioned elements — replace-the-frame.
|
|
144
|
+
*
|
|
145
|
+
* Each call describes everything that should be on screen; the host diffs it
|
|
146
|
+
* against the previous frame per device (elements with a stable `id` update
|
|
147
|
+
* in place; elements you stop sending are removed). There is no lifecycle to
|
|
148
|
+
* manage and no remove calls — `render([])` clears.
|
|
149
|
+
*
|
|
150
|
+
* Coordinates are raw pixels on the device's drawable canvas — read
|
|
151
|
+
* `session.capabilities.display` (populated on the "ready" event) for the
|
|
152
|
+
* real width/height and element budgets. Out-of-bounds boxes are clamped and
|
|
153
|
+
* over-budget elements are dropped tail-first; both are reported via the
|
|
154
|
+
* result, never silently.
|
|
155
|
+
*
|
|
156
|
+
* Awaiting is OPT-IN — a plain fire-and-forget call is fine. The returned
|
|
157
|
+
* promise never rejects.
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* const result = await session.display.render([
|
|
161
|
+
* {type: "text", id: "stats", box: {x: 12, y: 9, w: 200, h: 28}, text: "863 m 11 min"},
|
|
162
|
+
* {type: "image", id: "map", box: {x: 335, y: 14, w: 150, h: 150}, data: mapPng},
|
|
163
|
+
* {type: "rect", box: {x: 0, y: 0, w: 500, h: 220}, style: {radius: 4}},
|
|
164
|
+
* ])
|
|
165
|
+
* if (result.degraded) console.warn("dropped:", result.dropped)
|
|
166
|
+
*/
|
|
167
|
+
render(elements: RenderElement[], options: RenderOptions = {}): Promise<RenderResult> {
|
|
168
|
+
return this.session
|
|
169
|
+
.sendRequest<RenderResult>({
|
|
170
|
+
type: MiniappRequestType.RENDER,
|
|
171
|
+
view: options.view ?? "main",
|
|
172
|
+
elements,
|
|
173
|
+
durationMs: options.durationMs,
|
|
174
|
+
})
|
|
175
|
+
.catch((err) => ({
|
|
176
|
+
status: "blocked" as const,
|
|
177
|
+
reason:
|
|
178
|
+
typeof err === "object" && err !== null && "message" in err
|
|
179
|
+
? String((err as {message: unknown}).message)
|
|
180
|
+
: String(err),
|
|
181
|
+
}))
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Update the native nav UI's turn-by-turn card. */
|
|
185
|
+
sendNativeNavInfo(info: NativeNavInfo): Promise<NativeNavResult> {
|
|
186
|
+
return this.sendNativeNav({action: "info", info})
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Push the native nav UI's live mini map (base64 BMP/PNG; the host re-encodes
|
|
191
|
+
* and scales to the device's slot — 176×176 on G2). Draw north-up with the
|
|
192
|
+
* cursor centred; the device rotates the bitmap by `rotationDeg`.
|
|
193
|
+
*/
|
|
194
|
+
sendNativeNavMiniMap(data: string, options: {rotationDeg?: number; border?: boolean} = {}): Promise<NativeNavResult> {
|
|
195
|
+
return this.sendNativeNav({action: "mini_map", map: {data, ...options}})
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Push the native nav UI's overview (full-route) map — 576×188 on G2. */
|
|
199
|
+
sendNativeNavOverviewMap(data: string): Promise<NativeNavResult> {
|
|
200
|
+
return this.sendNativeNav({action: "overview_map", map: {data}})
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private sendNativeNav(payload: Record<string, unknown>): Promise<NativeNavResult> {
|
|
204
|
+
return this.session
|
|
205
|
+
.sendRequest<NativeNavResult>({type: MiniappRequestType.NATIVE_NAV, ...payload})
|
|
206
|
+
.catch((err) => ({
|
|
207
|
+
status: "unsupported" as const,
|
|
208
|
+
reason:
|
|
209
|
+
typeof err === "object" && err !== null && "message" in err
|
|
210
|
+
? String((err as {message: unknown}).message)
|
|
211
|
+
: String(err),
|
|
212
|
+
}))
|
|
213
|
+
}
|
|
214
|
+
}
|
|
@@ -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
|
+
}
|