@glassly/miniapp 0.3.0-dev.2 → 0.3.0-dev.462
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 +219 -74
- package/dist/background/index.d.ts +14 -8
- package/dist/background/index.d.ts.map +1 -1
- package/dist/background/index.js +4 -0
- package/dist/background/index.js.map +1 -1
- package/dist/background/register.d.ts +1 -1
- package/dist/background/register.js +1 -1
- package/dist/display/index.d.ts +23 -0
- package/dist/display/index.d.ts.map +1 -0
- package/dist/display/index.js +17 -0
- package/dist/display/index.js.map +1 -0
- package/dist/display/measurer/TextMeasurer.d.ts +160 -0
- package/dist/display/measurer/TextMeasurer.d.ts.map +1 -0
- package/dist/display/measurer/TextMeasurer.js +272 -0
- package/dist/display/measurer/TextMeasurer.js.map +1 -0
- package/dist/display/measurer/index.d.ts +10 -0
- package/dist/display/measurer/index.d.ts.map +1 -0
- package/dist/display/measurer/index.js +11 -0
- package/dist/display/measurer/index.js.map +1 -0
- package/dist/display/measurer/script-detection.d.ts +53 -0
- package/dist/display/measurer/script-detection.d.ts.map +1 -0
- package/dist/display/measurer/script-detection.js +184 -0
- package/dist/display/measurer/script-detection.js.map +1 -0
- package/dist/display/pixelArt.d.ts +22 -0
- package/dist/display/pixelArt.d.ts.map +1 -0
- package/dist/display/pixelArt.js +126 -0
- package/dist/display/pixelArt.js.map +1 -0
- package/dist/display/profiles/g1.d.ts +33 -0
- package/dist/display/profiles/g1.d.ts.map +1 -0
- package/dist/display/profiles/g1.js +214 -0
- package/dist/display/profiles/g1.js.map +1 -0
- package/dist/display/profiles/g2.d.ts +13 -0
- package/dist/display/profiles/g2.d.ts.map +1 -0
- package/dist/display/profiles/g2.js +34 -0
- package/dist/display/profiles/g2.js.map +1 -0
- package/dist/display/profiles/index.d.ts +12 -0
- package/dist/display/profiles/index.d.ts.map +1 -0
- package/dist/display/profiles/index.js +15 -0
- package/dist/display/profiles/index.js.map +1 -0
- package/dist/display/profiles/nex.d.ts +10 -0
- package/dist/display/profiles/nex.d.ts.map +1 -0
- package/dist/display/profiles/nex.js +150 -0
- package/dist/display/profiles/nex.js.map +1 -0
- package/dist/display/profiles/types.d.ts +103 -0
- package/dist/display/profiles/types.d.ts.map +1 -0
- package/dist/display/profiles/types.js +2 -0
- package/dist/display/profiles/types.js.map +1 -0
- package/dist/display/profiles/z100.d.ts +33 -0
- package/dist/display/profiles/z100.d.ts.map +1 -0
- package/dist/display/profiles/z100.js +182 -0
- package/dist/display/profiles/z100.js.map +1 -0
- package/dist/display/scene/types.d.ts +292 -0
- package/dist/display/scene/types.d.ts.map +1 -0
- package/dist/display/scene/types.js +0 -0
- package/dist/display/scene/types.js.map +1 -0
- package/dist/display/wrapper/TextWrapper.d.ts +102 -0
- package/dist/display/wrapper/TextWrapper.d.ts.map +1 -0
- package/dist/display/wrapper/TextWrapper.js +469 -0
- package/dist/display/wrapper/TextWrapper.js.map +1 -0
- package/dist/display/wrapper/index.d.ts +12 -0
- package/dist/display/wrapper/index.d.ts.map +1 -0
- package/dist/display/wrapper/index.js +12 -0
- package/dist/display/wrapper/index.js.map +1 -0
- package/dist/display/wrapper/types.d.ts +75 -0
- package/dist/display/wrapper/types.d.ts.map +1 -0
- package/dist/display/wrapper/types.js +11 -0
- package/dist/display/wrapper/types.js.map +1 -0
- package/dist/globals.d.ts +5 -5
- package/dist/globals.d.ts.map +1 -1
- package/dist/globals.js +1 -1
- package/dist/globals.js.map +1 -1
- package/dist/hardware.d.ts +4 -18
- package/dist/hardware.d.ts.map +1 -1
- package/dist/hardware.js +2 -13
- package/dist/hardware.js.map +1 -1
- package/dist/index.d.ts +16 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/knownApps.d.ts +26 -0
- package/dist/knownApps.d.ts.map +1 -0
- package/dist/knownApps.js +140 -0
- package/dist/knownApps.js.map +1 -0
- package/dist/mirror/canvas.d.ts +22 -0
- package/dist/mirror/canvas.d.ts.map +1 -0
- package/dist/mirror/canvas.js +311 -0
- package/dist/mirror/canvas.js.map +1 -0
- package/dist/mirror/geometry.d.ts +69 -0
- package/dist/mirror/geometry.d.ts.map +1 -0
- package/dist/mirror/geometry.js +75 -0
- package/dist/mirror/geometry.js.map +1 -0
- package/dist/mirror/index.d.ts +3 -0
- package/dist/mirror/index.d.ts.map +1 -0
- package/dist/mirror/index.js +2 -0
- package/dist/mirror/index.js.map +1 -0
- package/dist/mirror/types.d.ts +40 -0
- package/dist/mirror/types.d.ts.map +1 -0
- package/dist/mirror/types.js +2 -0
- package/dist/mirror/types.js.map +1 -0
- package/dist/modules/auth.d.ts +1 -1
- package/dist/modules/auth.d.ts.map +1 -1
- package/dist/modules/auth.js +2 -2
- package/dist/modules/auth.js.map +1 -1
- package/dist/modules/compass.d.ts +47 -0
- package/dist/modules/compass.d.ts.map +1 -0
- package/dist/modules/compass.js +39 -0
- package/dist/modules/compass.js.map +1 -0
- package/dist/modules/display.d.ts +192 -8
- package/dist/modules/display.d.ts.map +1 -1
- package/dist/modules/display.js +40 -3
- package/dist/modules/display.js.map +1 -1
- package/dist/modules/events.d.ts +30 -2
- package/dist/modules/events.d.ts.map +1 -1
- package/dist/modules/events.js.map +1 -1
- package/dist/modules/input.d.ts +40 -1
- package/dist/modules/input.d.ts.map +1 -1
- package/dist/modules/input.js +45 -1
- package/dist/modules/input.js.map +1 -1
- package/dist/modules/latest-render-scheduler.d.ts +19 -0
- package/dist/modules/latest-render-scheduler.d.ts.map +1 -0
- package/dist/modules/latest-render-scheduler.js +54 -0
- package/dist/modules/latest-render-scheduler.js.map +1 -0
- package/dist/modules/lightSensor.d.ts +44 -0
- package/dist/modules/lightSensor.d.ts.map +1 -0
- package/dist/modules/lightSensor.js +33 -0
- package/dist/modules/lightSensor.js.map +1 -0
- package/dist/modules/mic.d.ts +3 -2
- package/dist/modules/mic.d.ts.map +1 -1
- package/dist/modules/mic.js +3 -2
- 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/mirror.d.ts +16 -0
- package/dist/modules/mirror.d.ts.map +1 -0
- package/dist/modules/mirror.js +40 -0
- package/dist/modules/mirror.js.map +1 -0
- package/dist/modules/mono-text-rows.d.ts +20 -0
- package/dist/modules/mono-text-rows.d.ts.map +1 -0
- package/dist/modules/mono-text-rows.js +26 -0
- package/dist/modules/mono-text-rows.js.map +1 -0
- package/dist/modules/navigation.d.ts.map +1 -1
- package/dist/modules/navigation.js +21 -14
- package/dist/modules/navigation.js.map +1 -1
- package/dist/modules/phone.d.ts +26 -1
- package/dist/modules/phone.d.ts.map +1 -1
- package/dist/modules/phone.js +28 -0
- package/dist/modules/phone.js.map +1 -1
- package/dist/modules/settings.d.ts +45 -0
- package/dist/modules/settings.d.ts.map +1 -0
- package/dist/modules/settings.js +54 -0
- package/dist/modules/settings.js.map +1 -0
- package/dist/modules/system.d.ts +21 -0
- package/dist/modules/system.d.ts.map +1 -1
- package/dist/modules/system.js +12 -0
- package/dist/modules/system.js.map +1 -1
- package/dist/modules/transcription.d.ts +3 -2
- package/dist/modules/transcription.d.ts.map +1 -1
- package/dist/modules/transcription.js +3 -2
- package/dist/modules/transcription.js.map +1 -1
- package/dist/protocol.d.ts +65 -5
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +66 -5
- package/dist/protocol.js.map +1 -1
- package/dist/react/MirrorCanvas.d.ts +10 -0
- package/dist/react/MirrorCanvas.d.ts.map +1 -0
- package/dist/react/MirrorCanvas.js +23 -0
- package/dist/react/MirrorCanvas.js.map +1 -0
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +1 -0
- package/dist/react/index.js.map +1 -1
- package/dist/react/useColorScheme.d.ts +2 -3
- package/dist/react/useColorScheme.d.ts.map +1 -1
- package/dist/react/useColorScheme.js +15 -8
- package/dist/react/useColorScheme.js.map +1 -1
- package/dist/react/useRpc.js +2 -2
- package/dist/react/useRpc.js.map +1 -1
- package/dist/react/useSafeArea.d.ts.map +1 -1
- package/dist/react/useSafeArea.js +2 -2
- package/dist/react/useSafeArea.js.map +1 -1
- package/dist/session.d.ts +50 -4
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +35 -6
- package/dist/session.js.map +1 -1
- package/dist/transport/auto.d.ts +2 -2
- package/dist/transport/auto.js +2 -2
- package/dist/transport/dispatch.d.ts +1 -1
- package/dist/transport/dispatch.js +1 -1
- package/dist/transport/mock.d.ts +4 -4
- package/dist/transport/mock.d.ts.map +1 -1
- package/dist/transport/mock.js +31 -7
- package/dist/transport/mock.js.map +1 -1
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +1 -0
- package/dist/ui/index.js.map +1 -1
- package/package.json +19 -3
- package/src/background/index.ts +26 -4
- package/src/background/register.ts +1 -1
- package/src/display/index.ts +83 -0
- package/src/display/measurer/TextMeasurer.ts +346 -0
- package/src/display/measurer/index.ts +21 -0
- package/src/display/measurer/script-detection.ts +218 -0
- package/src/display/pixelArt.ts +141 -0
- package/src/display/profiles/g1.ts +237 -0
- package/src/display/profiles/g2.ts +39 -0
- package/src/display/profiles/index.ts +28 -0
- package/src/display/profiles/nex.ts +166 -0
- package/src/display/profiles/types.ts +132 -0
- package/src/display/profiles/z100.ts +198 -0
- package/src/display/scene/types.ts +335 -0
- package/src/display/wrapper/TextWrapper.ts +546 -0
- package/src/display/wrapper/index.ts +15 -0
- package/src/display/wrapper/types.ts +102 -0
- package/src/globals.ts +5 -5
- package/src/hardware.ts +6 -21
- package/src/index.ts +34 -6
- package/src/knownApps.ts +170 -0
- package/src/mirror/canvas.ts +322 -0
- package/src/mirror/geometry.ts +128 -0
- package/src/mirror/index.ts +2 -0
- package/src/mirror/types.ts +38 -0
- package/src/modules/auth.ts +2 -2
- package/src/modules/compass.ts +69 -0
- package/src/modules/display.ts +222 -12
- package/src/modules/events.ts +27 -1
- package/src/modules/input.ts +55 -2
- package/src/modules/latest-render-scheduler.ts +55 -0
- package/src/modules/lightSensor.ts +61 -0
- package/src/modules/mic.ts +3 -2
- package/src/modules/miniapps.ts +2 -0
- package/src/modules/mirror.ts +42 -0
- package/src/modules/mono-text-rows.ts +30 -0
- package/src/modules/navigation.ts +20 -14
- package/src/modules/phone.ts +40 -1
- package/src/modules/settings.ts +64 -0
- package/src/modules/system.ts +30 -1
- package/src/modules/transcription.ts +3 -2
- package/src/protocol.ts +72 -5
- package/src/react/MirrorCanvas.tsx +35 -0
- package/src/react/index.ts +3 -0
- package/src/react/useColorScheme.ts +17 -8
- package/src/react/useRpc.ts +2 -2
- package/src/react/useSafeArea.ts +2 -2
- package/src/session.ts +78 -8
- package/src/transport/auto.ts +2 -2
- package/src/transport/dispatch.ts +1 -1
- package/src/transport/mock.ts +42 -11
- package/src/ui/index.ts +3 -0
- package/dist/modules/heading.d.ts +0 -16
- package/dist/modules/heading.d.ts.map +0 -1
- package/dist/modules/heading.js +0 -20
- package/dist/modules/heading.js.map +0 -1
- package/src/modules/heading.ts +0 -23
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opt-in producer pacing for any miniapp. One render promise in flight and
|
|
3
|
+
* one replaceable pending callback; no scene is built until it can be sent.
|
|
4
|
+
* This bounds SDK work, not BLE packets, and does not cancel native work.
|
|
5
|
+
*/
|
|
6
|
+
export class LatestRenderScheduler {
|
|
7
|
+
private pending: (() => Promise<void>) | undefined
|
|
8
|
+
private timer: ReturnType<typeof setTimeout> | undefined
|
|
9
|
+
private busy = false
|
|
10
|
+
private disposed = false
|
|
11
|
+
private nextAt = 0
|
|
12
|
+
|
|
13
|
+
constructor(private readonly intervalMs: () => number) {}
|
|
14
|
+
|
|
15
|
+
request(render: () => Promise<void>): void {
|
|
16
|
+
if (this.disposed) return
|
|
17
|
+
this.pending = render
|
|
18
|
+
this.pump()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
clearPending(): void {
|
|
22
|
+
this.pending = undefined
|
|
23
|
+
if (this.timer !== undefined) clearTimeout(this.timer)
|
|
24
|
+
this.timer = undefined
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
dispose(): void {
|
|
28
|
+
this.disposed = true
|
|
29
|
+
this.clearPending()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private pump(): void {
|
|
33
|
+
if (this.disposed || this.busy || !this.pending || this.timer !== undefined) return
|
|
34
|
+
const delay = this.nextAt - Date.now()
|
|
35
|
+
if (delay > 0) {
|
|
36
|
+
this.timer = setTimeout(() => {
|
|
37
|
+
this.timer = undefined
|
|
38
|
+
this.pump()
|
|
39
|
+
}, delay)
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
const render = this.pending
|
|
43
|
+
this.pending = undefined
|
|
44
|
+
this.busy = true
|
|
45
|
+
this.nextAt = Date.now() + Math.max(0, this.intervalMs())
|
|
46
|
+
// Callers handle/report errors; still release the scheduler after failure.
|
|
47
|
+
void Promise.resolve()
|
|
48
|
+
.then(() => (this.disposed ? undefined : render()))
|
|
49
|
+
.catch(() => {})
|
|
50
|
+
.finally(() => {
|
|
51
|
+
this.busy = false
|
|
52
|
+
this.pump()
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/** Ambient light sensor exposed by G2 custom firmware. Subscribing does not start hardware. */
|
|
2
|
+
import {MiniappRequestType, MiniappStreamType} from "../protocol"
|
|
3
|
+
import type {MiniappSession} from "../session"
|
|
4
|
+
import type {UnsubscribeFn} from "./events"
|
|
5
|
+
|
|
6
|
+
export interface LightSensorOptions {
|
|
7
|
+
/** 100..5000 ms, default 500. */
|
|
8
|
+
intervalMs?: number
|
|
9
|
+
/** Change threshold in firmware ALS units, 0..65535, default 1. */
|
|
10
|
+
minDelta?: number
|
|
11
|
+
/** Report unchanged readings every 0..65535 ms; 0 disables, default 5000. */
|
|
12
|
+
heartbeatMs?: number
|
|
13
|
+
}
|
|
14
|
+
export interface LightSensorData {
|
|
15
|
+
reason: "query" | "started" | "poll" | "stopped"
|
|
16
|
+
opened: boolean
|
|
17
|
+
passive: boolean
|
|
18
|
+
autoBrightness: boolean
|
|
19
|
+
readSucceeded: boolean
|
|
20
|
+
status: number
|
|
21
|
+
/** Stock calibrated ALS units (approximately tenths of lux), not a lux measurement. */
|
|
22
|
+
value: number
|
|
23
|
+
peak: number
|
|
24
|
+
targetLevel: number
|
|
25
|
+
brightness: number
|
|
26
|
+
gear: number
|
|
27
|
+
scaleQ10: number
|
|
28
|
+
/** Firmware uptime, wraps at 2^32 ms. */
|
|
29
|
+
sampleTimestampMs: number
|
|
30
|
+
/** Phone reception time, Unix milliseconds. */
|
|
31
|
+
timestamp: number
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class LightSensorModule {
|
|
35
|
+
constructor(private readonly session: MiniappSession) {}
|
|
36
|
+
onUpdate(handler: (data: LightSensorData) => void): UnsubscribeFn {
|
|
37
|
+
return this.session._subscribe(MiniappStreamType.LIGHT_SENSOR, handler as (data: unknown) => void)
|
|
38
|
+
}
|
|
39
|
+
/** Request the cached driver reading; delivered by onUpdate, without opening the sensor. */
|
|
40
|
+
query(): Promise<void> {
|
|
41
|
+
return this.control("query")
|
|
42
|
+
}
|
|
43
|
+
/** Passive polling temporarily suspends stock automatic brightness adjustments.
|
|
44
|
+
* Navigation pauses polling; stopping or closing the miniapp restores stock behavior.
|
|
45
|
+
* Polling also stops when the CFW display lease is released or expires.
|
|
46
|
+
*/
|
|
47
|
+
start(options: LightSensorOptions = {}): Promise<void> {
|
|
48
|
+
return this.control("start", options)
|
|
49
|
+
}
|
|
50
|
+
stop(): Promise<void> {
|
|
51
|
+
return this.control("stop")
|
|
52
|
+
}
|
|
53
|
+
private control(operation: string, options: LightSensorOptions = {}): Promise<void> {
|
|
54
|
+
return this.session.sendRequest({
|
|
55
|
+
type: MiniappRequestType.GLASSES_SENSOR_CONTROL,
|
|
56
|
+
sensor: "light",
|
|
57
|
+
operation,
|
|
58
|
+
options,
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
}
|
package/src/modules/mic.ts
CHANGED
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
* Audio *output* (TTS, file playback) lives on `session.speaker`.
|
|
12
12
|
*
|
|
13
13
|
* MICROPHONE permission must be declared in miniapp.json for any of these
|
|
14
|
-
* subscriptions to succeed
|
|
15
|
-
* PERMISSION_NOT_DECLARED
|
|
14
|
+
* subscriptions to succeed. Otherwise the phone runtime drops them silently:
|
|
15
|
+
* SUBSCRIBE carries no requestId, so the PERMISSION_NOT_DECLARED result is
|
|
16
|
+
* discarded and handlers just never fire.
|
|
16
17
|
*/
|
|
17
18
|
|
|
18
19
|
import {MiniappRequestType, MiniappStreamType} from "../protocol"
|
package/src/modules/miniapps.ts
CHANGED
|
@@ -42,6 +42,8 @@ export interface MiniappInfo {
|
|
|
42
42
|
description?: string
|
|
43
43
|
version: string
|
|
44
44
|
running: boolean
|
|
45
|
+
/** The wearer keeps the app off the home grid; menus should leave it out too. Absent on older hosts. */
|
|
46
|
+
hidden?: boolean
|
|
45
47
|
compatibility: MiniappCompatibility
|
|
46
48
|
/** Declared actions (empty if the miniapp declares none). */
|
|
47
49
|
actions: MiniappActionInfo[]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import {MiniappRequestType, MiniappStreamType} from "../protocol"
|
|
2
|
+
import type {MiniappSession} from "../session"
|
|
3
|
+
import type {UnsubscribeFn} from "./events"
|
|
4
|
+
import type {MirrorSnapshot} from "../mirror/types"
|
|
5
|
+
|
|
6
|
+
/** Read the globally visible, intended glasses display. Requires DISPLAY_MIRROR. */
|
|
7
|
+
export class MirrorModule {
|
|
8
|
+
private latest: MirrorSnapshot | null = null
|
|
9
|
+
private listeners = 0
|
|
10
|
+
|
|
11
|
+
constructor(private readonly session: MiniappSession) {}
|
|
12
|
+
|
|
13
|
+
get hasPermission(): boolean {
|
|
14
|
+
return this.session._hasManifestPermission("DISPLAY_MIRROR")
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Query the host's current canvas. This is a preview, not hardware readback. */
|
|
18
|
+
getCurrent(): Promise<MirrorSnapshot> {
|
|
19
|
+
return this.session.sendRequest<MirrorSnapshot>({type: MiniappRequestType.MIRROR_GET})
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Receive an initial snapshot and subsequent changes. Dispose when the preview closes. */
|
|
23
|
+
onUpdate(handler: (snapshot: MirrorSnapshot) => void): UnsubscribeFn {
|
|
24
|
+
this.listeners++
|
|
25
|
+
let active = true
|
|
26
|
+
const off = this.session._subscribe(MiniappStreamType.MIRROR_UPDATE, (data) => {
|
|
27
|
+
this.latest = data as MirrorSnapshot
|
|
28
|
+
handler(this.latest)
|
|
29
|
+
})
|
|
30
|
+
const cached = this.latest
|
|
31
|
+
if (cached)
|
|
32
|
+
queueMicrotask(() => {
|
|
33
|
+
if (active && this.latest === cached) handler(cached)
|
|
34
|
+
})
|
|
35
|
+
return () => {
|
|
36
|
+
if (!active) return
|
|
37
|
+
active = false
|
|
38
|
+
off()
|
|
39
|
+
if (--this.listeners === 0) this.latest = null
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type {RenderElement} from "./display"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Split a fixed-pitch grid into stable row elements. On G2 CFW, unsupported
|
|
5
|
+
* Unicode then rasterizes only its row instead of the entire grid. This is
|
|
6
|
+
* opt-in: callers must check the device's text-element budget. It uses the
|
|
7
|
+
* G2 mono renderer's 2px inset and rounded 1.2x line pitch, not stock metrics.
|
|
8
|
+
* Include every returned row in each replacement scene (including blanks).
|
|
9
|
+
*/
|
|
10
|
+
export function monoTextRows(options: {
|
|
11
|
+
id: string
|
|
12
|
+
text: string
|
|
13
|
+
box: {x: number; y: number; w: number; h: number}
|
|
14
|
+
fontSize: number
|
|
15
|
+
}): RenderElement[] {
|
|
16
|
+
const {id, text, box, fontSize} = options
|
|
17
|
+
if (![box.x, box.y, box.w, box.h, fontSize].every(Number.isFinite) || fontSize <= 0) return []
|
|
18
|
+
if (box.w <= 2 || box.h <= 2) return []
|
|
19
|
+
const pitch = Math.max(1, Math.round(fontSize * 1.2))
|
|
20
|
+
return text
|
|
21
|
+
.split("\n")
|
|
22
|
+
.slice(0, Math.ceil((box.h - 2) / pitch))
|
|
23
|
+
.map((line, index) => ({
|
|
24
|
+
type: "text",
|
|
25
|
+
id: `${id}-row-${index}`,
|
|
26
|
+
text: line,
|
|
27
|
+
box: {x: box.x, y: box.y + index * pitch, w: box.w, h: Math.min(pitch + 2, box.h - index * pitch)},
|
|
28
|
+
style: {font: "mono", fontSize},
|
|
29
|
+
}))
|
|
30
|
+
}
|
|
@@ -342,21 +342,27 @@ export class NavigationModule {
|
|
|
342
342
|
}
|
|
343
343
|
const restRoute = restResult.routes[0]
|
|
344
344
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
345
|
+
let nativeResult: {ok: boolean; error?: string}
|
|
346
|
+
try {
|
|
347
|
+
nativeResult = await this.session.sendRequest<{ok: boolean; error?: string}>({
|
|
348
|
+
type: MiniappRequestType.NAVIGATION_START,
|
|
349
|
+
// Keep lat/lng on the wire for hosts not yet upgraded to read `stops`.
|
|
350
|
+
lat: stops[0]?.lat,
|
|
351
|
+
lng: stops[0]?.lng,
|
|
352
|
+
stops,
|
|
353
|
+
mode,
|
|
354
|
+
avoid: opts.avoid,
|
|
355
|
+
simulate: opts.simulate ?? false,
|
|
356
|
+
speedMultiplier: opts.speedMultiplier ?? 1,
|
|
357
|
+
missedTurnRerouteMeters: opts.missedTurnRerouteMeters,
|
|
358
|
+
})
|
|
359
|
+
} catch (err) {
|
|
360
|
+
// Wire-level failure (host threw, timed out, disconnected). Honor the
|
|
361
|
+
// never-throws contract and release the pivot subscriptions.
|
|
362
|
+
return failStart(err instanceof Error ? err.message : String(err))
|
|
363
|
+
}
|
|
357
364
|
if (!nativeResult.ok) {
|
|
358
|
-
|
|
359
|
-
return nativeResult
|
|
365
|
+
return failStart(nativeResult.error ?? "navigation.start: host refused the trip")
|
|
360
366
|
}
|
|
361
367
|
|
|
362
368
|
// Dispatch the REST route through the native route channel so subscribers see step roads now.
|
package/src/modules/phone.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* session.phone.notifications.{on, hasPermission, stop}
|
|
7
7
|
* session.phone.calendar.{listEvents, hasPermission}
|
|
8
|
+
* session.phone.heading.{onUpdate, hasPermission}
|
|
8
9
|
* session.phone.onBattery(...) // stays flat
|
|
9
10
|
*
|
|
10
11
|
* Imperative phone-OS calls (share, openUrl, copyToClipboard, download) live
|
|
@@ -18,7 +19,7 @@
|
|
|
18
19
|
|
|
19
20
|
import {MiniappRequestType, MiniappStreamType} from "../protocol"
|
|
20
21
|
import {MiniappSession} from "../session"
|
|
21
|
-
import type {BatteryData, NotificationDismissedData, PhoneNotificationData, UnsubscribeFn} from "./events"
|
|
22
|
+
import type {BatteryData, HeadingData, NotificationDismissedData, PhoneNotificationData, UnsubscribeFn} from "./events"
|
|
22
23
|
|
|
23
24
|
class TrackedSubs {
|
|
24
25
|
private readonly unsubs = new Set<UnsubscribeFn>()
|
|
@@ -71,6 +72,27 @@ export class PhoneNotificationsModule extends TrackedSubs {
|
|
|
71
72
|
get hasPermission(): boolean {
|
|
72
73
|
return this.session._hasManifestPermission("READ_NOTIFICATIONS")
|
|
73
74
|
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Apps that post phone notifications, for a per-app filter UI. Android
|
|
78
|
+
* lists installed apps with their icons (base64 PNG); iOS has no cross-app
|
|
79
|
+
* lookup, so it lists a seeded catalog plus the apps the glasses reported
|
|
80
|
+
* posting, without icons. SYSTEM-only.
|
|
81
|
+
*/
|
|
82
|
+
async installedApps(): Promise<NotificationAppInfo[]> {
|
|
83
|
+
const result = await this.session.sendRequest<NotificationAppInfo[]>({
|
|
84
|
+
type: MiniappRequestType.NOTIFICATION_APPS_LIST,
|
|
85
|
+
})
|
|
86
|
+
return result ?? []
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface NotificationAppInfo {
|
|
91
|
+
/** Bundle id (iOS) or package name (Android). */
|
|
92
|
+
packageName: string
|
|
93
|
+
appName: string
|
|
94
|
+
/** Base64 PNG, Android only. */
|
|
95
|
+
icon: string | null
|
|
74
96
|
}
|
|
75
97
|
|
|
76
98
|
export interface CalendarListOptions {
|
|
@@ -136,13 +158,30 @@ function normalizeCalendarDate(value: string | Date | undefined, field: string):
|
|
|
136
158
|
return date.toISOString()
|
|
137
159
|
}
|
|
138
160
|
|
|
161
|
+
/** Phone compass (magnetometer / sensor fusion). For the glasses' own heading use `session.compass`. */
|
|
162
|
+
export class PhoneHeadingModule {
|
|
163
|
+
constructor(private readonly session: MiniappSession) {}
|
|
164
|
+
|
|
165
|
+
/** True iff `LOCATION` is declared in the miniapp's manifest. */
|
|
166
|
+
get hasPermission(): boolean {
|
|
167
|
+
return this.session._hasManifestPermission("LOCATION")
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Continuous phone heading updates; the phone compass starts on the first subscriber. */
|
|
171
|
+
onUpdate(handler: (data: HeadingData) => void): UnsubscribeFn {
|
|
172
|
+
return this.session._subscribe(MiniappStreamType.HEADING_UPDATE, handler as (data: unknown) => void)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
139
176
|
export class PhoneModule {
|
|
140
177
|
public readonly notifications: PhoneNotificationsModule
|
|
141
178
|
public readonly calendar: PhoneCalendarModule
|
|
179
|
+
public readonly heading: PhoneHeadingModule
|
|
142
180
|
|
|
143
181
|
constructor(private readonly session: MiniappSession) {
|
|
144
182
|
this.notifications = new PhoneNotificationsModule(session)
|
|
145
183
|
this.calendar = new PhoneCalendarModule(session)
|
|
184
|
+
this.heading = new PhoneHeadingModule(session)
|
|
146
185
|
}
|
|
147
186
|
|
|
148
187
|
/**
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview SettingsModule — `session.settings`, the engine's settings
|
|
3
|
+
* store behind an allowlist. SYSTEM-only: every call rejects with
|
|
4
|
+
* NOT_PERMITTED unless the caller is a system app. The dashboard miniapp is
|
|
5
|
+
* the first consumer: it reads and edits the same dashboard, notification
|
|
6
|
+
* and glasses-menu keys the phone's settings screens used to, so firmware
|
|
7
|
+
* sync and the host's notification policy keep one source of truth.
|
|
8
|
+
*
|
|
9
|
+
* Keys are the engine's setting keys (e.g. `custom_dashboard_layout`,
|
|
10
|
+
* `notifications_popups`, `menu_apps`); values are their JSON values. The
|
|
11
|
+
* host also serves a few derived read-only keys (`phone_active`,
|
|
12
|
+
* `foreground_app_on_screen`, `ring_battery`) that the engine computes
|
|
13
|
+
* rather than stores. `set` on a read-only or unknown key rejects.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import {MiniappRequestType, MiniappStreamType} from "../protocol"
|
|
17
|
+
import {MiniappSession} from "../session"
|
|
18
|
+
import type {UnsubscribeFn} from "./events"
|
|
19
|
+
|
|
20
|
+
export interface SettingsChange {
|
|
21
|
+
key: string
|
|
22
|
+
value: unknown
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class SettingsModule {
|
|
26
|
+
constructor(private readonly session: MiniappSession) {}
|
|
27
|
+
|
|
28
|
+
/** One allowlisted key's value; undefined when the key is unknown. SYSTEM-only. */
|
|
29
|
+
async get<T = unknown>(key: string): Promise<T | undefined> {
|
|
30
|
+
const values = await this.getAll([key])
|
|
31
|
+
return values[key] as T | undefined
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Every allowlisted key's value, or just `keys`. Unknown keys are left out
|
|
36
|
+
* rather than rejected, so a newer miniapp on an older host still reads
|
|
37
|
+
* the keys the host knows. SYSTEM-only.
|
|
38
|
+
*/
|
|
39
|
+
async getAll(keys?: string[]): Promise<Record<string, unknown>> {
|
|
40
|
+
const result = await this.session.sendRequest<{values?: Record<string, unknown>}>({
|
|
41
|
+
type: MiniappRequestType.SETTINGS_GET,
|
|
42
|
+
...(keys ? {keys} : {}),
|
|
43
|
+
})
|
|
44
|
+
return result?.values ?? {}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Write a writable key through the engine's settings store: persistence,
|
|
49
|
+
* server sync and the Bluetooth sync of glasses settings behave as if a
|
|
50
|
+
* phone screen wrote it. SYSTEM-only.
|
|
51
|
+
*/
|
|
52
|
+
async set(key: string, value: unknown): Promise<void> {
|
|
53
|
+
await this.session.sendRequest<void>({type: MiniappRequestType.SETTINGS_SET, key, value})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* An allowlisted key changed (including the derived read-only keys). On
|
|
58
|
+
* subscribe the host replays every key's current value, so a mirror can
|
|
59
|
+
* be built from this stream alone. SYSTEM-only; other apps never hear it.
|
|
60
|
+
*/
|
|
61
|
+
onChange(handler: (change: SettingsChange) => void): UnsubscribeFn {
|
|
62
|
+
return this.session._subscribe(MiniappStreamType.SETTINGS_CHANGED, handler as (data: unknown) => void)
|
|
63
|
+
}
|
|
64
|
+
}
|
package/src/modules/system.ts
CHANGED
|
@@ -46,6 +46,22 @@ export interface ScanQrOptions {
|
|
|
46
46
|
|
|
47
47
|
export type ScanQrResult = {data: string; cancelled?: false} | {cancelled: true; data?: undefined}
|
|
48
48
|
|
|
49
|
+
/** Native G2 performance counters only; never display text or packet contents. */
|
|
50
|
+
export interface DisplayDiagnostic {
|
|
51
|
+
v: 1
|
|
52
|
+
run: string
|
|
53
|
+
id: number
|
|
54
|
+
t: number
|
|
55
|
+
type: "sample" | "stall" | "session"
|
|
56
|
+
[key: string]: string | number | boolean
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface DisplayDiagnosticsResult {
|
|
60
|
+
enabled: boolean
|
|
61
|
+
generatedAt: number
|
|
62
|
+
records: DisplayDiagnostic[]
|
|
63
|
+
}
|
|
64
|
+
|
|
49
65
|
export class SystemModule {
|
|
50
66
|
constructor(private readonly session: MiniappSession) {}
|
|
51
67
|
|
|
@@ -66,6 +82,19 @@ export class SystemModule {
|
|
|
66
82
|
})
|
|
67
83
|
}
|
|
68
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Read recent native G2 diagnostics (bounded to 180 records / 3 minutes).
|
|
87
|
+
* Requires the phone's Debug Console setting. Empty on unsupported devices;
|
|
88
|
+
* submitted bytes are not over-the-air throughput or measured display FPS.
|
|
89
|
+
* Poll no faster than once per second. Older hosts reject this request.
|
|
90
|
+
*/
|
|
91
|
+
async getDisplayDiagnostics(): Promise<DisplayDiagnosticsResult> {
|
|
92
|
+
const result = await this.session.sendRequest<DisplayDiagnosticsResult>({
|
|
93
|
+
type: MiniappRequestType.DISPLAY_DIAGNOSTICS,
|
|
94
|
+
})
|
|
95
|
+
return result ?? {enabled: false, generatedAt: Date.now(), records: []}
|
|
96
|
+
}
|
|
97
|
+
|
|
69
98
|
/** Copy text to the system clipboard. */
|
|
70
99
|
async copyToClipboard(text: string): Promise<void> {
|
|
71
100
|
await this.session.sendRequest<void>({
|
|
@@ -108,6 +137,6 @@ export class SystemModule {
|
|
|
108
137
|
},
|
|
109
138
|
{timeoutMs: 0},
|
|
110
139
|
)
|
|
111
|
-
return result ?? {
|
|
140
|
+
return result ?? {cancelled: true}
|
|
112
141
|
}
|
|
113
142
|
}
|
|
@@ -14,8 +14,9 @@
|
|
|
14
14
|
* module still exposes lower-level audio chunks + VAD; this module is for
|
|
15
15
|
* "give me text from speech."
|
|
16
16
|
*
|
|
17
|
-
* MICROPHONE permission must be declared in miniapp.json.
|
|
18
|
-
*
|
|
17
|
+
* MICROPHONE permission must be declared in miniapp.json. Otherwise the phone
|
|
18
|
+
* runtime drops the subscription silently: SUBSCRIBE carries no requestId, so
|
|
19
|
+
* the PERMISSION_NOT_DECLARED result is discarded and handlers just never fire.
|
|
19
20
|
*/
|
|
20
21
|
|
|
21
22
|
import {MiniappRequestType, MiniappStreamType} from "../protocol"
|
package/src/protocol.ts
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* These values are the contract between @glassly/miniapp (running in a WebView)
|
|
6
6
|
* and LocalMiniappRuntime (running on the phone).
|
|
7
7
|
*
|
|
8
|
-
* IMPORTANT: This file has
|
|
9
|
-
* wire
|
|
10
|
-
*
|
|
8
|
+
* IMPORTANT: This file has no runtime dependency on the retired cloud SDK.
|
|
9
|
+
* Its published wire-protocol enums are used only for compatibility with
|
|
10
|
+
* existing cloud-hosted miniapps, not for phone↔miniapp communication.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
// ============================================================================
|
|
@@ -24,6 +24,13 @@ export enum MiniappRequestType {
|
|
|
24
24
|
/** Update the set of streams the miniapp is subscribed to. */
|
|
25
25
|
SUBSCRIBE = "miniapp_subscribe",
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Declare the touch gestures this miniapp is handling itself. The most
|
|
29
|
+
* recent claimer of a gesture gets it alone and the host's default for it
|
|
30
|
+
* is skipped; releasing hands it back to the previous claimer.
|
|
31
|
+
*/
|
|
32
|
+
CLAIM_GESTURES = "miniapp_claim_gestures",
|
|
33
|
+
|
|
27
34
|
/**
|
|
28
35
|
* Push a legacy display layout to the glasses. The SDK no longer emits this
|
|
29
36
|
* (render()/RENDER replaced it); hosts keep accepting it from bundles packed
|
|
@@ -34,6 +41,17 @@ export enum MiniappRequestType {
|
|
|
34
41
|
/** Render a whole scene of positioned elements (replace-the-frame). */
|
|
35
42
|
RENDER = "miniapp_render",
|
|
36
43
|
|
|
44
|
+
/** Read the globally visible intended glasses canvas. DISPLAY_MIRROR permission. */
|
|
45
|
+
MIRROR_GET = "miniapp_mirror_get",
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Ask the host for a display view (display.showView). "main" is every
|
|
49
|
+
* app's; "dashboard" is the overlay slot above the main content, granted
|
|
50
|
+
* only to the running miniapp whose manifest type is "system_dashboard"
|
|
51
|
+
* while the glasses can show it.
|
|
52
|
+
*/
|
|
53
|
+
SHOW_VIEW = "miniapp_show_view",
|
|
54
|
+
|
|
37
55
|
/**
|
|
38
56
|
* Feed the device's firmware-rendered navigation UI (capability
|
|
39
57
|
* `hasNativeNavigation`): structured turn-by-turn card fields and
|
|
@@ -70,6 +88,21 @@ export enum MiniappRequestType {
|
|
|
70
88
|
/** Read a bounded snapshot of phone calendar events. */
|
|
71
89
|
CALENDAR_LIST_EVENTS = "miniapp_calendar_list_events",
|
|
72
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Apps that post phone notifications, for a notification filter UI.
|
|
93
|
+
* SYSTEM-only. Android: installed apps with icons; iOS: the seeded
|
|
94
|
+
* catalog plus apps the glasses reported.
|
|
95
|
+
*/
|
|
96
|
+
NOTIFICATION_APPS_LIST = "miniapp_notification_apps_list",
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The engine's settings store behind an allowlist (session.settings).
|
|
100
|
+
* SYSTEM-only. GET takes optional `keys` and answers `{values}`; SET
|
|
101
|
+
* takes `key` and `value` and writes through the store.
|
|
102
|
+
*/
|
|
103
|
+
SETTINGS_GET = "miniapp_settings_get",
|
|
104
|
+
SETTINGS_SET = "miniapp_settings_set",
|
|
105
|
+
|
|
73
106
|
/** Start a turn-by-turn navigation trip. Android only. */
|
|
74
107
|
NAVIGATION_START = "miniapp_navigation_start",
|
|
75
108
|
/** Stop the active navigation trip (if any). */
|
|
@@ -175,6 +208,7 @@ export enum MiniappRequestType {
|
|
|
175
208
|
* for callers who want to control the sensor directly. G2 only today.
|
|
176
209
|
*/
|
|
177
210
|
IMU_SET_ENABLED = "miniapp_imu_set_enabled",
|
|
211
|
+
GLASSES_SENSOR_CONTROL = "miniapp_glasses_sensor_control",
|
|
178
212
|
|
|
179
213
|
/** Explicitly enable/disable glasses-side VAD (GX8002). Mirrors the app's mic settings toggle. */
|
|
180
214
|
MIC_SET_VAD_ENABLED = "miniapp_mic_set_vad_enabled",
|
|
@@ -192,6 +226,7 @@ export enum MiniappRequestType {
|
|
|
192
226
|
/** Open a URL in the system browser. */
|
|
193
227
|
OPEN_URL = "miniapp_open_url",
|
|
194
228
|
/** Copy text to the system clipboard. */
|
|
229
|
+
DISPLAY_DIAGNOSTICS = "miniapp_display_diagnostics",
|
|
195
230
|
COPY_CLIPBOARD = "miniapp_copy_clipboard",
|
|
196
231
|
/** Download a file (triggers OS share sheet for save location). */
|
|
197
232
|
DOWNLOAD = "miniapp_download",
|
|
@@ -295,6 +330,9 @@ export enum MiniappResponseType {
|
|
|
295
330
|
/** Push: glasses capabilities changed. */
|
|
296
331
|
CAPABILITIES_UPDATE = "miniapp_capabilities_update",
|
|
297
332
|
|
|
333
|
+
/** Push: host user preferences (units, clock format) changed. */
|
|
334
|
+
PREFERENCES_UPDATE = "miniapp_preferences_update",
|
|
335
|
+
|
|
298
336
|
/** Push: miniapp's foreground/background state changed. */
|
|
299
337
|
VISIBILITY_CHANGE = "miniapp_visibility_change",
|
|
300
338
|
|
|
@@ -352,7 +390,25 @@ export enum MiniappStreamType {
|
|
|
352
390
|
// Hardware / input events
|
|
353
391
|
BUTTON_PRESS = "button_press",
|
|
354
392
|
TOUCH_EVENT = "touch_event",
|
|
393
|
+
/**
|
|
394
|
+
* Physical keyboard paired to the glasses (input.onKey): DOM-style
|
|
395
|
+
* `{phase, key, code, modifiers, text?, repeat, timestamp}`. G2 Glassly CFW
|
|
396
|
+
* revision 39+ only; broadcast to every subscriber like button_press.
|
|
397
|
+
*/
|
|
398
|
+
KEYBOARD_EVENT = "keyboard_event",
|
|
355
399
|
HEAD_POSITION = "head_position",
|
|
400
|
+
/**
|
|
401
|
+
* The dashboard view's state (display.onViewChange): `{kind: "change",
|
|
402
|
+
* view, reason}` when the host grants or takes the overlay slot.
|
|
403
|
+
*/
|
|
404
|
+
DISPLAY_VIEW = "display_view",
|
|
405
|
+
/** Initial mirror snapshot and subsequent visible display changes. */
|
|
406
|
+
MIRROR_UPDATE = "mirror_update",
|
|
407
|
+
/**
|
|
408
|
+
* A gesture claim changed hands (input.onClaimChange): `{gesture, held}`,
|
|
409
|
+
* sent to the app that gained or lost the gesture.
|
|
410
|
+
*/
|
|
411
|
+
GESTURE_CLAIM = "gesture_claim",
|
|
356
412
|
/**
|
|
357
413
|
* Raw accelerometer reading from the glasses IMU — `{x, y, z}` in g, plus a
|
|
358
414
|
* timestamp. G2 only today. A richer combined IMU stream (accel + gyro +
|
|
@@ -375,19 +431,30 @@ export enum MiniappStreamType {
|
|
|
375
431
|
|
|
376
432
|
// Phone sensors
|
|
377
433
|
LOCATION_UPDATE = "location_update",
|
|
378
|
-
/**
|
|
434
|
+
/** Phone compass heading in degrees (0=N, 90=E). */
|
|
379
435
|
HEADING_UPDATE = "heading_update",
|
|
436
|
+
// Glasses sensors
|
|
437
|
+
LIGHT_SENSOR = "light_sensor",
|
|
438
|
+
/** Glasses compass heading; auto-starts on custom firmware, nav mode only on stock. */
|
|
439
|
+
GLASSES_COMPASS = "glasses_compass",
|
|
440
|
+
COMPASS_CALIBRATION = "compass_calibration",
|
|
380
441
|
/** Turn-by-turn navigation event (maneuver / rerouting / arrived / error). */
|
|
381
442
|
NAVIGATION_UPDATE = "navigation_update",
|
|
382
443
|
/** Active navigation route polyline — full path, fired once per route build. */
|
|
383
444
|
NAVIGATION_ROUTE = "navigation_route",
|
|
384
445
|
PHONE_NOTIFICATION = "phone_notification",
|
|
446
|
+
/**
|
|
447
|
+
* An allowlisted engine setting changed (session.settings.onChange), as
|
|
448
|
+
* `{key, value}`. SYSTEM-only; every key's current value is replayed on
|
|
449
|
+
* subscribe.
|
|
450
|
+
*/
|
|
451
|
+
SETTINGS_CHANGED = "settings_changed",
|
|
385
452
|
/**
|
|
386
453
|
* A previously-posted notification was dismissed by the user.
|
|
387
454
|
*
|
|
388
455
|
* Android-only today. iOS does not expose dismiss callbacks to apps
|
|
389
456
|
* (Apple privacy restriction); subscribing on iOS succeeds but no events
|
|
390
|
-
* fire. See agents/miniapp-speaker-state-and-notif-dismissed-plan.md.
|
|
457
|
+
* fire. See notes/archive/agents/miniapp-speaker-state-and-notif-dismissed-plan.md.
|
|
391
458
|
*/
|
|
392
459
|
PHONE_NOTIFICATION_DISMISSED = "phone_notification_dismissed",
|
|
393
460
|
// Photos, streaming
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {useEffect, useRef, type CSSProperties} from "react"
|
|
2
|
+
import {MirrorCanvasRenderer} from "../mirror/canvas"
|
|
3
|
+
import type {MirrorSnapshot} from "../mirror/types"
|
|
4
|
+
|
|
5
|
+
export interface MirrorCanvasProps {
|
|
6
|
+
frame: MirrorSnapshot | null
|
|
7
|
+
className?: string
|
|
8
|
+
style?: CSSProperties
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Browser preview of intended glasses output; pass snapshots over a UI channel. */
|
|
12
|
+
export function MirrorCanvas({frame, className, style}: MirrorCanvasProps) {
|
|
13
|
+
const canvas = useRef<HTMLCanvasElement>(null)
|
|
14
|
+
const renderer = useRef<MirrorCanvasRenderer | null>(null)
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (!canvas.current) return
|
|
17
|
+
renderer.current = new MirrorCanvasRenderer(canvas.current)
|
|
18
|
+
return () => {
|
|
19
|
+
renderer.current?.dispose()
|
|
20
|
+
renderer.current = null
|
|
21
|
+
}
|
|
22
|
+
}, [])
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (frame) renderer.current?.update(frame)
|
|
25
|
+
}, [frame])
|
|
26
|
+
return (
|
|
27
|
+
<canvas
|
|
28
|
+
ref={canvas}
|
|
29
|
+
className={className}
|
|
30
|
+
role="img"
|
|
31
|
+
aria-label="Glasses display mirror"
|
|
32
|
+
style={{display: "block", width: "100%", height: "auto", ...style}}
|
|
33
|
+
/>
|
|
34
|
+
)
|
|
35
|
+
}
|
package/src/react/index.ts
CHANGED
|
@@ -10,3 +10,6 @@ export type {MiniappHeaderProps} from "./MiniappHeader"
|
|
|
10
10
|
export {MiniappProvider} from "./MiniappProvider"
|
|
11
11
|
export type {MiniappProviderProps} from "./MiniappProvider"
|
|
12
12
|
export {useRpc, type RpcCallable} from "./useRpc"
|
|
13
|
+
|
|
14
|
+
export {MirrorCanvas, type MirrorCanvasProps} from "./MirrorCanvas"
|
|
15
|
+
export type {MirrorSnapshot, MirrorContent, MirrorElement} from "../mirror/types"
|