@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.
Files changed (312) hide show
  1. package/README.md +275 -0
  2. package/dist/background/index.d.ts +64 -0
  3. package/dist/background/index.d.ts.map +1 -0
  4. package/dist/background/index.js +26 -0
  5. package/dist/background/index.js.map +1 -0
  6. package/dist/background/register.d.ts +39 -0
  7. package/dist/background/register.d.ts.map +1 -0
  8. package/dist/background/register.js +72 -0
  9. package/dist/background/register.js.map +1 -0
  10. package/dist/dev-reload.d.ts +21 -0
  11. package/dist/dev-reload.d.ts.map +1 -0
  12. package/dist/dev-reload.js +60 -0
  13. package/dist/dev-reload.js.map +1 -0
  14. package/dist/envelope.d.ts +23 -0
  15. package/dist/envelope.d.ts.map +1 -0
  16. package/dist/envelope.js +47 -0
  17. package/dist/envelope.js.map +1 -0
  18. package/dist/globals.d.ts +54 -0
  19. package/dist/globals.d.ts.map +1 -0
  20. package/dist/globals.js +12 -0
  21. package/dist/globals.js.map +1 -0
  22. package/dist/hardware.d.ts +38 -0
  23. package/dist/hardware.d.ts.map +1 -0
  24. package/dist/hardware.js +31 -0
  25. package/dist/hardware.js.map +1 -0
  26. package/dist/index.d.ts +65 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +34 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/modules/actions.d.ts +81 -0
  31. package/dist/modules/actions.d.ts.map +1 -0
  32. package/dist/modules/actions.js +137 -0
  33. package/dist/modules/actions.js.map +1 -0
  34. package/dist/modules/ai.d.ts +211 -0
  35. package/dist/modules/ai.d.ts.map +1 -0
  36. package/dist/modules/ai.js +117 -0
  37. package/dist/modules/ai.js.map +1 -0
  38. package/dist/modules/auth.d.ts +25 -0
  39. package/dist/modules/auth.d.ts.map +1 -0
  40. package/dist/modules/auth.js +52 -0
  41. package/dist/modules/auth.js.map +1 -0
  42. package/dist/modules/base64.d.ts +16 -0
  43. package/dist/modules/base64.d.ts.map +1 -0
  44. package/dist/modules/base64.js +92 -0
  45. package/dist/modules/base64.js.map +1 -0
  46. package/dist/modules/blob.d.ts +161 -0
  47. package/dist/modules/blob.d.ts.map +1 -0
  48. package/dist/modules/blob.js +277 -0
  49. package/dist/modules/blob.js.map +1 -0
  50. package/dist/modules/camera.d.ts +183 -0
  51. package/dist/modules/camera.d.ts.map +1 -0
  52. package/dist/modules/camera.js +119 -0
  53. package/dist/modules/camera.js.map +1 -0
  54. package/dist/modules/cloud.d.ts +34 -0
  55. package/dist/modules/cloud.d.ts.map +1 -0
  56. package/dist/modules/cloud.js +70 -0
  57. package/dist/modules/cloud.js.map +1 -0
  58. package/dist/modules/dashboard.d.ts +24 -0
  59. package/dist/modules/dashboard.d.ts.map +1 -0
  60. package/dist/modules/dashboard.js +39 -0
  61. package/dist/modules/dashboard.js.map +1 -0
  62. package/dist/modules/display.d.ts +167 -0
  63. package/dist/modules/display.d.ts.map +1 -0
  64. package/dist/modules/display.js +83 -0
  65. package/dist/modules/display.js.map +1 -0
  66. package/dist/modules/events.d.ts +178 -0
  67. package/dist/modules/events.d.ts.map +1 -0
  68. package/dist/modules/events.js +168 -0
  69. package/dist/modules/events.js.map +1 -0
  70. package/dist/modules/glasses.d.ts +36 -0
  71. package/dist/modules/glasses.d.ts.map +1 -0
  72. package/dist/modules/glasses.js +52 -0
  73. package/dist/modules/glasses.js.map +1 -0
  74. package/dist/modules/heading.d.ts +16 -0
  75. package/dist/modules/heading.d.ts.map +1 -0
  76. package/dist/modules/heading.js +20 -0
  77. package/dist/modules/heading.js.map +1 -0
  78. package/dist/modules/imu.d.ts +31 -0
  79. package/dist/modules/imu.d.ts.map +1 -0
  80. package/dist/modules/imu.js +40 -0
  81. package/dist/modules/imu.js.map +1 -0
  82. package/dist/modules/input.d.ts +34 -0
  83. package/dist/modules/input.d.ts.map +1 -0
  84. package/dist/modules/input.js +50 -0
  85. package/dist/modules/input.js.map +1 -0
  86. package/dist/modules/keys.d.ts +30 -0
  87. package/dist/modules/keys.d.ts.map +1 -0
  88. package/dist/modules/keys.js +37 -0
  89. package/dist/modules/keys.js.map +1 -0
  90. package/dist/modules/languages.d.ts +42 -0
  91. package/dist/modules/languages.d.ts.map +1 -0
  92. package/dist/modules/languages.js +57 -0
  93. package/dist/modules/languages.js.map +1 -0
  94. package/dist/modules/led.d.ts +35 -0
  95. package/dist/modules/led.d.ts.map +1 -0
  96. package/dist/modules/led.js +39 -0
  97. package/dist/modules/led.js.map +1 -0
  98. package/dist/modules/location.d.ts +27 -0
  99. package/dist/modules/location.d.ts.map +1 -0
  100. package/dist/modules/location.js +35 -0
  101. package/dist/modules/location.js.map +1 -0
  102. package/dist/modules/memory.d.ts +126 -0
  103. package/dist/modules/memory.d.ts.map +1 -0
  104. package/dist/modules/memory.js +141 -0
  105. package/dist/modules/memory.js.map +1 -0
  106. package/dist/modules/mic.d.ts +61 -0
  107. package/dist/modules/mic.d.ts.map +1 -0
  108. package/dist/modules/mic.js +94 -0
  109. package/dist/modules/mic.js.map +1 -0
  110. package/dist/modules/miniapps.d.ts +82 -0
  111. package/dist/modules/miniapps.d.ts.map +1 -0
  112. package/dist/modules/miniapps.js +58 -0
  113. package/dist/modules/miniapps.js.map +1 -0
  114. package/dist/modules/navigation.d.ts +332 -0
  115. package/dist/modules/navigation.d.ts.map +1 -0
  116. package/dist/modules/navigation.js +387 -0
  117. package/dist/modules/navigation.js.map +1 -0
  118. package/dist/modules/permissions.d.ts +58 -0
  119. package/dist/modules/permissions.d.ts.map +1 -0
  120. package/dist/modules/permissions.js +66 -0
  121. package/dist/modules/permissions.js.map +1 -0
  122. package/dist/modules/phone.d.ts +95 -0
  123. package/dist/modules/phone.d.ts.map +1 -0
  124. package/dist/modules/phone.js +110 -0
  125. package/dist/modules/phone.js.map +1 -0
  126. package/dist/modules/pivots/engine.d.ts +169 -0
  127. package/dist/modules/pivots/engine.d.ts.map +1 -0
  128. package/dist/modules/pivots/engine.js +778 -0
  129. package/dist/modules/pivots/engine.js.map +1 -0
  130. package/dist/modules/pivots/geometry.d.ts +84 -0
  131. package/dist/modules/pivots/geometry.d.ts.map +1 -0
  132. package/dist/modules/pivots/geometry.js +371 -0
  133. package/dist/modules/pivots/geometry.js.map +1 -0
  134. package/dist/modules/pivots/instructions.d.ts +139 -0
  135. package/dist/modules/pivots/instructions.d.ts.map +1 -0
  136. package/dist/modules/pivots/instructions.js +281 -0
  137. package/dist/modules/pivots/instructions.js.map +1 -0
  138. package/dist/modules/speaker.d.ts +175 -0
  139. package/dist/modules/speaker.d.ts.map +1 -0
  140. package/dist/modules/speaker.js +249 -0
  141. package/dist/modules/speaker.js.map +1 -0
  142. package/dist/modules/speakers.d.ts +29 -0
  143. package/dist/modules/speakers.d.ts.map +1 -0
  144. package/dist/modules/speakers.js +38 -0
  145. package/dist/modules/speakers.js.map +1 -0
  146. package/dist/modules/storage.d.ts +50 -0
  147. package/dist/modules/storage.d.ts.map +1 -0
  148. package/dist/modules/storage.js +99 -0
  149. package/dist/modules/storage.js.map +1 -0
  150. package/dist/modules/stream.d.ts +133 -0
  151. package/dist/modules/stream.d.ts.map +1 -0
  152. package/dist/modules/stream.js +53 -0
  153. package/dist/modules/stream.js.map +1 -0
  154. package/dist/modules/system.d.ts +71 -0
  155. package/dist/modules/system.d.ts.map +1 -0
  156. package/dist/modules/system.js +65 -0
  157. package/dist/modules/system.js.map +1 -0
  158. package/dist/modules/transcription.d.ts +99 -0
  159. package/dist/modules/transcription.d.ts.map +1 -0
  160. package/dist/modules/transcription.js +126 -0
  161. package/dist/modules/transcription.js.map +1 -0
  162. package/dist/modules/translation.d.ts +65 -0
  163. package/dist/modules/translation.d.ts.map +1 -0
  164. package/dist/modules/translation.js +121 -0
  165. package/dist/modules/translation.js.map +1 -0
  166. package/dist/modules/ui.d.ts +186 -0
  167. package/dist/modules/ui.d.ts.map +1 -0
  168. package/dist/modules/ui.js +289 -0
  169. package/dist/modules/ui.js.map +1 -0
  170. package/dist/protocol.d.ts +384 -0
  171. package/dist/protocol.d.ts.map +1 -0
  172. package/dist/protocol.js +413 -0
  173. package/dist/protocol.js.map +1 -0
  174. package/dist/react/MiniappHeader.d.ts +44 -0
  175. package/dist/react/MiniappHeader.d.ts.map +1 -0
  176. package/dist/react/MiniappHeader.js +32 -0
  177. package/dist/react/MiniappHeader.js.map +1 -0
  178. package/dist/react/MiniappProvider.d.ts +33 -0
  179. package/dist/react/MiniappProvider.d.ts.map +1 -0
  180. package/dist/react/MiniappProvider.js +15 -0
  181. package/dist/react/MiniappProvider.js.map +1 -0
  182. package/dist/react/capsuleHeaderLayout.d.ts +3 -0
  183. package/dist/react/capsuleHeaderLayout.d.ts.map +1 -0
  184. package/dist/react/capsuleHeaderLayout.js +5 -0
  185. package/dist/react/capsuleHeaderLayout.js.map +1 -0
  186. package/dist/react/index.d.ts +13 -0
  187. package/dist/react/index.d.ts.map +1 -0
  188. package/dist/react/index.js +9 -0
  189. package/dist/react/index.js.map +1 -0
  190. package/dist/react/useCapabilities.d.ts +26 -0
  191. package/dist/react/useCapabilities.d.ts.map +1 -0
  192. package/dist/react/useCapabilities.js +27 -0
  193. package/dist/react/useCapabilities.js.map +1 -0
  194. package/dist/react/useCapsuleHeaderStyle.d.ts +27 -0
  195. package/dist/react/useCapsuleHeaderStyle.d.ts.map +1 -0
  196. package/dist/react/useCapsuleHeaderStyle.js +39 -0
  197. package/dist/react/useCapsuleHeaderStyle.js.map +1 -0
  198. package/dist/react/useColorScheme.d.ts +18 -0
  199. package/dist/react/useColorScheme.d.ts.map +1 -0
  200. package/dist/react/useColorScheme.js +24 -0
  201. package/dist/react/useColorScheme.js.map +1 -0
  202. package/dist/react/useConnected.d.ts +17 -0
  203. package/dist/react/useConnected.d.ts.map +1 -0
  204. package/dist/react/useConnected.js +39 -0
  205. package/dist/react/useConnected.js.map +1 -0
  206. package/dist/react/useRpc.d.ts +29 -0
  207. package/dist/react/useRpc.d.ts.map +1 -0
  208. package/dist/react/useRpc.js +82 -0
  209. package/dist/react/useRpc.js.map +1 -0
  210. package/dist/react/useSafeArea.d.ts +21 -0
  211. package/dist/react/useSafeArea.d.ts.map +1 -0
  212. package/dist/react/useSafeArea.js +22 -0
  213. package/dist/react/useSafeArea.js.map +1 -0
  214. package/dist/session.d.ts +355 -0
  215. package/dist/session.d.ts.map +1 -0
  216. package/dist/session.js +688 -0
  217. package/dist/session.js.map +1 -0
  218. package/dist/sub-path-types.test-d.d.ts +40 -0
  219. package/dist/sub-path-types.test-d.d.ts.map +1 -0
  220. package/dist/sub-path-types.test-d.js +37 -0
  221. package/dist/sub-path-types.test-d.js.map +1 -0
  222. package/dist/transport/auto.d.ts +35 -0
  223. package/dist/transport/auto.d.ts.map +1 -0
  224. package/dist/transport/auto.js +124 -0
  225. package/dist/transport/auto.js.map +1 -0
  226. package/dist/transport/dispatch.d.ts +43 -0
  227. package/dist/transport/dispatch.d.ts.map +1 -0
  228. package/dist/transport/dispatch.js +91 -0
  229. package/dist/transport/dispatch.js.map +1 -0
  230. package/dist/transport/local-socket.d.ts +28 -0
  231. package/dist/transport/local-socket.d.ts.map +1 -0
  232. package/dist/transport/local-socket.js +80 -0
  233. package/dist/transport/local-socket.js.map +1 -0
  234. package/dist/transport/mock.d.ts +54 -0
  235. package/dist/transport/mock.d.ts.map +1 -0
  236. package/dist/transport/mock.js +210 -0
  237. package/dist/transport/mock.js.map +1 -0
  238. package/dist/transport/postmessage.d.ts +36 -0
  239. package/dist/transport/postmessage.d.ts.map +1 -0
  240. package/dist/transport/postmessage.js +78 -0
  241. package/dist/transport/postmessage.js.map +1 -0
  242. package/dist/transport/types.d.ts +25 -0
  243. package/dist/transport/types.d.ts.map +1 -0
  244. package/dist/transport/types.js +11 -0
  245. package/dist/transport/types.js.map +1 -0
  246. package/dist/ui/index.d.ts +107 -0
  247. package/dist/ui/index.d.ts.map +1 -0
  248. package/dist/ui/index.js +32 -0
  249. package/dist/ui/index.js.map +1 -0
  250. package/package.json +84 -0
  251. package/src/background/index.ts +177 -0
  252. package/src/background/register.ts +89 -0
  253. package/src/dev-reload.ts +59 -0
  254. package/src/envelope.ts +54 -0
  255. package/src/globals.ts +62 -0
  256. package/src/hardware.ts +40 -0
  257. package/src/index.ts +216 -0
  258. package/src/modules/actions.ts +189 -0
  259. package/src/modules/ai.ts +281 -0
  260. package/src/modules/auth.ts +68 -0
  261. package/src/modules/base64.ts +87 -0
  262. package/src/modules/blob.ts +341 -0
  263. package/src/modules/camera.ts +258 -0
  264. package/src/modules/cloud.ts +94 -0
  265. package/src/modules/dashboard.ts +51 -0
  266. package/src/modules/display.ts +207 -0
  267. package/src/modules/events.ts +318 -0
  268. package/src/modules/glasses.ts +58 -0
  269. package/src/modules/heading.ts +23 -0
  270. package/src/modules/imu.ts +44 -0
  271. package/src/modules/input.ts +66 -0
  272. package/src/modules/keys.ts +46 -0
  273. package/src/modules/languages.ts +72 -0
  274. package/src/modules/led.ts +61 -0
  275. package/src/modules/location.ts +39 -0
  276. package/src/modules/memory.ts +224 -0
  277. package/src/modules/mic.ts +103 -0
  278. package/src/modules/miniapps.ts +103 -0
  279. package/src/modules/navigation.ts +674 -0
  280. package/src/modules/permissions.ts +83 -0
  281. package/src/modules/phone.ts +155 -0
  282. package/src/modules/pivots/engine.ts +863 -0
  283. package/src/modules/pivots/geometry.ts +409 -0
  284. package/src/modules/pivots/instructions.ts +341 -0
  285. package/src/modules/speaker.ts +347 -0
  286. package/src/modules/speakers.ts +40 -0
  287. package/src/modules/storage.ts +109 -0
  288. package/src/modules/stream.ts +168 -0
  289. package/src/modules/system.ts +113 -0
  290. package/src/modules/transcription.ts +180 -0
  291. package/src/modules/translation.ts +156 -0
  292. package/src/modules/ui.ts +481 -0
  293. package/src/protocol.ts +475 -0
  294. package/src/react/MiniappHeader.tsx +114 -0
  295. package/src/react/MiniappProvider.tsx +50 -0
  296. package/src/react/capsuleHeaderLayout.ts +11 -0
  297. package/src/react/index.ts +12 -0
  298. package/src/react/useCapabilities.ts +29 -0
  299. package/src/react/useCapsuleHeaderStyle.ts +55 -0
  300. package/src/react/useColorScheme.ts +26 -0
  301. package/src/react/useConnected.ts +47 -0
  302. package/src/react/useRpc.ts +103 -0
  303. package/src/react/useSafeArea.ts +36 -0
  304. package/src/session.ts +969 -0
  305. package/src/sub-path-types.test-d.ts +60 -0
  306. package/src/transport/auto.ts +143 -0
  307. package/src/transport/dispatch.ts +106 -0
  308. package/src/transport/local-socket.ts +91 -0
  309. package/src/transport/mock.ts +255 -0
  310. package/src/transport/postmessage.ts +95 -0
  311. package/src/transport/types.ts +26 -0
  312. package/src/ui/index.ts +129 -0
@@ -0,0 +1,258 @@
1
+ /**
2
+ * @fileoverview CameraModule — glasses camera control and photo capture.
3
+ */
4
+
5
+ import {MiniappRequestType} from "../protocol"
6
+ import {MiniappSession} from "../session"
7
+
8
+ export type CameraRoiPosition = "center" | "bottom" | "top"
9
+ export type CameraFovPreset = "narrow" | "standard" | "wide"
10
+
11
+ export type CameraFovRequest =
12
+ | {
13
+ /** Horizontal FOV, degrees. */
14
+ fov: number
15
+ /** Crop/region position: center, bottom, or top. Defaults to center. */
16
+ roiPosition?: CameraRoiPosition
17
+ }
18
+ | {
19
+ /** Named FOV preset. Presets use center ROI. */
20
+ preset: CameraFovPreset
21
+ }
22
+
23
+ export type SetCameraFovOptions = CameraFovRequest
24
+
25
+ export interface CameraFovResult {
26
+ requestId: string
27
+ fov: number
28
+ roiPosition: CameraRoiPosition
29
+ timestamp: number
30
+ }
31
+
32
+ export interface TakePhotoOptions {
33
+ size?: "low" | "medium" | "high" | "max"
34
+ /** Capture at maximum source quality and optimize BLE delivery for readable text. */
35
+ mode?: "photo" | "text"
36
+ /**
37
+ * Image delivery path. `auto` tries direct Wi-Fi upload and falls back to
38
+ * BLE; `direct` disables BLE fallback; `ble` always relays through the phone.
39
+ */
40
+ transferMethod?: "auto" | "direct" | "ble"
41
+ compress?: "none" | "low" | "medium" | "high"
42
+ sound?: boolean
43
+ saveToGallery?: boolean
44
+ /**
45
+ * Manual shutter / exposure time in nanoseconds. Omit (or pass undefined)
46
+ * to let the glasses auto-expose. Honored only on cameras that support
47
+ * manual exposure; ignored otherwise.
48
+ */
49
+ exposureTimeNs?: number
50
+ /** Sensor ISO for this capture only. Only used when `exposureTimeNs` enables manual exposure. */
51
+ iso?: number | null
52
+ /** After AE convergence, divide the metered exposure by this factor (scan mode). */
53
+ aeExposureDivisor?: number
54
+ /** Cap ISO after AE metering (scan mode). */
55
+ isoCap?: number
56
+ /** Request per-capture noise reduction. Sent on the wire; glasses may log `not_implemented`. */
57
+ noiseReduction?: boolean
58
+ /** Request per-capture edge enhancement. Sent on the wire; glasses may log `not_implemented`. */
59
+ edgeEnhancement?: boolean
60
+ /**
61
+ * ZSL buffering. Manual and scan stills force it off because fixed sensor controls take priority.
62
+ */
63
+ zsl?: boolean
64
+ /**
65
+ * MFNR still capture. Manual and scan stills force it off because fixed sensor controls take priority.
66
+ */
67
+ mfnr?: boolean
68
+ /** ISP digital gain hint. */
69
+ ispDigitalGain?: number
70
+ /** ISP analog gain hint. */
71
+ ispAnalogGain?: string
72
+ /**
73
+ * Override the SDK's default 60s request timeout for this capture only.
74
+ * Leave unset for a normal user-triggered capture (the full 60s ceiling is
75
+ * appropriate there). Callers that speculatively fire a capture and may
76
+ * abandon it if the result isn't needed (e.g. a short-lived non-visual
77
+ * assistant turn racing a capture against a ~1s latency cap) should pass a
78
+ * short value here so the abandoned `takePhoto()` promise settles quickly
79
+ * instead of sitting on the default 60s ceiling.
80
+ */
81
+ timeoutMs?: number
82
+ }
83
+
84
+ export interface PhotoTaken {
85
+ /** Request id that correlates ASG status, upload, and phone/cloud logs. */
86
+ requestId: string
87
+ photoUrl: string
88
+ mimeType: string
89
+ size: number
90
+ }
91
+
92
+ export interface WarmUpCameraOptions {
93
+ size?: "low" | "medium" | "high" | "max"
94
+ /** Match the upcoming capture mode so warm-up and capture share one ASG camera session. */
95
+ mode?: "photo" | "text"
96
+ exposureTimeNs?: number
97
+ /** Ready-state hold in milliseconds. Defaults to 15 seconds and is capped at 60 seconds. */
98
+ durationMs?: number
99
+ /** ZSL preview buffering for the warm-up session. */
100
+ zsl?: boolean
101
+ /** MFNR still capture for the warm-up session. */
102
+ mfnr?: boolean
103
+ }
104
+
105
+ export interface StartVideoRecordingOptions {
106
+ /** Video width in pixels. Omit to use the device's saved button-video default. */
107
+ width?: number
108
+ /** Video height in pixels. Omit to use the device's saved button-video default. */
109
+ height?: number
110
+ /**
111
+ * Frames per second. Lower frame rates keep the glasses cooler and produce
112
+ * smaller files — useful for long recordings where smooth motion isn't needed.
113
+ * Omit to use the device default.
114
+ */
115
+ fps?: number
116
+ /** Play start/stop capture sounds. Defaults to true. */
117
+ sound?: boolean
118
+ /** Keep the recording on the glasses after it finishes. Defaults to false. */
119
+ save?: boolean
120
+ }
121
+
122
+ export interface VideoRecordingStarted {
123
+ /** Identifier for this recording session; pass to {@link stopVideoRecording}. */
124
+ recordingId: string
125
+ }
126
+
127
+ export interface StopVideoRecordingOptions {
128
+ /**
129
+ * Upload the finished recording to this URL with a multipart POST (the file is
130
+ * the `video` part). The glasses upload it directly. Omit to keep the recording
131
+ * on the glasses instead.
132
+ */
133
+ uploadUrl?: string
134
+ /** Bearer token sent as the `Authorization` header on the upload, if your endpoint needs one. */
135
+ uploadAuthToken?: string
136
+ }
137
+
138
+ export class CameraModule {
139
+ constructor(private readonly session: MiniappSession) {}
140
+
141
+ /** True iff `CAMERA` is declared in the miniapp's manifest. */
142
+ get hasPermission(): boolean {
143
+ return this.session._hasManifestPermission("CAMERA")
144
+ }
145
+
146
+ /**
147
+ * Apply a session-owned camera FOV/ROI override on the glasses.
148
+ *
149
+ * Resolves after the ASG client reports that the setting was applied to camera
150
+ * hardware after the restart cooldown. The host restores the prior override or persistent base
151
+ * when this miniapp closes. Requires CAMERA permission declared in miniapp.json.
152
+ */
153
+ async setFov(request: CameraFovRequest): Promise<CameraFovResult> {
154
+ return this.session.sendRequest<CameraFovResult>({
155
+ type: MiniappRequestType.CAMERA_FOV,
156
+ ...request,
157
+ })
158
+ }
159
+
160
+ /**
161
+ * Take a photo via the glasses camera. Returns a URL to the captured image.
162
+ * Requires CAMERA permission declared in miniapp.json.
163
+ *
164
+ * The photo is uploaded to cloud storage; the returned URL is a short-TTL
165
+ * (~30 minute) signed download URL. If the glasses don't have a camera,
166
+ * the phone-side handler rejects with an error. Check
167
+ * `session.capabilities.hasCamera` before calling.
168
+ */
169
+ async takePhoto(options: TakePhotoOptions = {}): Promise<PhotoTaken> {
170
+ return this.session.sendRequest<PhotoTaken>(
171
+ {
172
+ type: MiniappRequestType.PHOTO,
173
+ size: options.size ?? "medium",
174
+ mode: options.mode ?? "photo",
175
+ ...(options.transferMethod !== undefined ? {transferMethod: options.transferMethod} : {}),
176
+ compress: options.compress ?? "none",
177
+ sound: options.sound ?? true,
178
+ saveToGallery: options.saveToGallery ?? false,
179
+ exposureTimeNs: options.exposureTimeNs,
180
+ iso: options.iso,
181
+ aeExposureDivisor: options.aeExposureDivisor,
182
+ isoCap: options.isoCap,
183
+ noiseReduction: options.noiseReduction,
184
+ edgeEnhancement: options.edgeEnhancement,
185
+ zsl: options.zsl,
186
+ mfnr: options.mfnr,
187
+ ispDigitalGain: options.ispDigitalGain,
188
+ ispAnalogGain: options.ispAnalogGain,
189
+ },
190
+ options.timeoutMs != null ? {timeoutMs: options.timeoutMs} : undefined,
191
+ )
192
+ }
193
+
194
+ /**
195
+ * Pre-warm the glasses camera so the next takePhoto() is near-instant.
196
+ * The camera stays warm for ~durationMs (default 15s, maximum 60s); call warmUp() again to
197
+ * extend it. Warm with the same `size` you'll capture with — a mismatched size
198
+ * forces the camera to reconfigure and loses the speedup. Requires CAMERA
199
+ * permission in miniapp.json. Resolves once the camera reports ready. The host automatically
200
+ * releases this miniapp's request-owned lease when the miniapp closes.
201
+ *
202
+ * Warm-ups are serialized: only one runs at a time and none may start while a
203
+ * photo is being captured. If the camera is busy (a capture is in flight, or
204
+ * another warm-up is still opening) this rejects with `camera_busy` — retry
205
+ * shortly, or just call takePhoto() (it works regardless, only slower). The
206
+ * normal warmUp() → takePhoto() sequence never hits this.
207
+ */
208
+ async warmUp(options: WarmUpCameraOptions = {}): Promise<void> {
209
+ await this.session.sendRequest<void>({
210
+ type: MiniappRequestType.CAMERA_WARM_UP,
211
+ size: options.size ?? "medium",
212
+ mode: options.mode ?? "photo",
213
+ exposureTimeNs: options.exposureTimeNs,
214
+ durationMs: options.durationMs ?? 15000,
215
+ zsl: options.zsl,
216
+ mfnr: options.mfnr,
217
+ })
218
+ }
219
+
220
+ /**
221
+ * Start recording video on the glasses camera. Returns a `recordingId` to pass
222
+ * to {@link stopVideoRecording} after the glasses report that recording
223
+ * started. Requires CAMERA permission declared in miniapp.json. Check
224
+ * `session.capabilities.hasCamera` before calling.
225
+ *
226
+ * Resolution and frame rate are optional — omit them to use the device's saved
227
+ * button-video settings. Lowering `fps` (e.g. to 5) keeps the glasses cooler
228
+ * during long recordings.
229
+ */
230
+ async startVideoRecording(options: StartVideoRecordingOptions = {}): Promise<VideoRecordingStarted> {
231
+ return this.session.sendRequest<VideoRecordingStarted>({
232
+ type: MiniappRequestType.VIDEO_RECORDING_START,
233
+ width: options.width,
234
+ height: options.height,
235
+ fps: options.fps,
236
+ sound: options.sound ?? true,
237
+ save: options.save ?? false,
238
+ })
239
+ }
240
+
241
+ /**
242
+ * Stop an in-progress video recording started with {@link startVideoRecording}.
243
+ *
244
+ * By default the recording stays on the glasses. Pass `uploadUrl` to have the
245
+ * glasses upload the finished clip to your own endpoint.
246
+ *
247
+ * @param recordingId The id returned from `startVideoRecording`.
248
+ * @param options Optional upload destination for the finished recording.
249
+ */
250
+ async stopVideoRecording(recordingId: string, options: StopVideoRecordingOptions = {}): Promise<void> {
251
+ await this.session.sendRequest<void>({
252
+ type: MiniappRequestType.VIDEO_RECORDING_STOP,
253
+ recordingId,
254
+ uploadUrl: options.uploadUrl,
255
+ uploadAuthToken: options.uploadAuthToken,
256
+ })
257
+ }
258
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * @fileoverview CloudModule — local miniapp view of the phone-owned cloud client.
3
+ *
4
+ * Miniapps do not own or configure the cloud connection. The phone owns one
5
+ * cloud-client instance and pushes status snapshots here so miniapps can render
6
+ * honest UX such as online/offline captions indicators.
7
+ */
8
+
9
+ import {EventEmitter} from "eventemitter3"
10
+
11
+ import {MiniappSession} from "../session"
12
+ import type {UnsubscribeFn} from "./events"
13
+
14
+ export type CloudClientConnectionStatus = "connected" | "connecting" | "reconnecting" | "disconnected"
15
+ export type CloudClientAudioTransport = "udp" | "ws" | "offline" | "none"
16
+
17
+ export interface CloudClientStatus {
18
+ status: CloudClientConnectionStatus
19
+ audioTransport: CloudClientAudioTransport
20
+ }
21
+
22
+ type CloudEmitterEvents = {
23
+ status: (status: CloudClientStatus) => void
24
+ }
25
+
26
+ export const CLOUD_STATUS_STREAM = "_cloud_status"
27
+
28
+ const DEFAULT_STATUS: CloudClientStatus = {
29
+ status: "disconnected",
30
+ audioTransport: "none",
31
+ }
32
+
33
+ function normalizeCloudStatus(data: unknown): CloudClientStatus | null {
34
+ if (!data || typeof data !== "object") return null
35
+ const raw = data as Record<string, unknown>
36
+ const status = raw.status
37
+ const audioTransport = raw.audioTransport
38
+
39
+ if (status !== "connected" && status !== "connecting" && status !== "reconnecting" && status !== "disconnected") {
40
+ return null
41
+ }
42
+ if (
43
+ audioTransport !== "udp" &&
44
+ audioTransport !== "ws" &&
45
+ audioTransport !== "offline" &&
46
+ audioTransport !== "none"
47
+ ) {
48
+ return null
49
+ }
50
+
51
+ return {status, audioTransport}
52
+ }
53
+
54
+ export class CloudModule {
55
+ private readonly emitter = new EventEmitter<CloudEmitterEvents>()
56
+ private current: CloudClientStatus = {...DEFAULT_STATUS}
57
+
58
+ constructor(private readonly session: MiniappSession) {
59
+ this.session._subscribe(CLOUD_STATUS_STREAM, (data) => {
60
+ const next = normalizeCloudStatus(data)
61
+ if (!next) return
62
+ if (next.status === this.current.status && next.audioTransport === this.current.audioTransport) return
63
+ this.current = next
64
+ this.emitter.emit("status", {...this.current})
65
+ })
66
+ }
67
+
68
+ /** Latest status snapshot pushed by the phone runtime. */
69
+ get status(): CloudClientStatus {
70
+ return {...this.current}
71
+ }
72
+
73
+ /** True when the phone's cloud-client runtime handshake is complete. */
74
+ get connected(): boolean {
75
+ return this.current.status === "connected"
76
+ }
77
+
78
+ /** Method alias for callers who prefer function-style state reads. */
79
+ isConnected(): boolean {
80
+ return this.connected
81
+ }
82
+
83
+ /**
84
+ * Subscribe to cloud-client status updates. The current value is delivered
85
+ * immediately so UI code can hydrate without waiting for the next transition.
86
+ */
87
+ onStatusChanged(handler: (status: CloudClientStatus) => void): UnsubscribeFn {
88
+ handler({...this.current})
89
+ this.emitter.on("status", handler)
90
+ return () => {
91
+ this.emitter.off("status", handler)
92
+ }
93
+ }
94
+ }
@@ -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,207 @@
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
+ font?: "default" | "mono"
62
+ /** Font size in px for `font: "mono"`. Default 20. */
63
+ fontSize?: number
64
+ }
65
+
66
+ export interface RenderRectStyle {
67
+ border?: number
68
+ radius?: number
69
+ }
70
+
71
+ /**
72
+ * One element of a rendered scene. `id` is optional but recommended for
73
+ * anything that updates over time: elements with a stable id update in place on
74
+ * the glasses (no flicker); the host matches unnamed elements by geometry.
75
+ */
76
+ export type RenderElement =
77
+ | {type: "text"; id?: string; box: RenderBox; text: string; style?: RenderTextStyle}
78
+ | {type: "image"; id?: string; box: RenderBox; data: string}
79
+ | {type: "rect"; id?: string; box: RenderBox; style?: RenderRectStyle}
80
+
81
+ export interface RenderOptions {
82
+ view?: ViewType
83
+ /** Auto-clear after this many ms (same semantics as legacy display options). */
84
+ durationMs?: number
85
+ }
86
+
87
+ // ============================================================================
88
+ // Native navigation UI (capability `hasNativeNavigation`, e.g. G2)
89
+ // ============================================================================
90
+
91
+ /** Turn-by-turn card fields — pre-formatted display strings, rendered verbatim by the device. */
92
+ export interface NativeNavInfo {
93
+ /** Firmware direction-icon index, 0…35. */
94
+ directionIcon: number
95
+ /** Distance to the next maneuver, e.g. "500 m". */
96
+ distance: string
97
+ roadName: string
98
+ /** Remaining trip duration, e.g. "11 min". */
99
+ spendTime: string
100
+ /** Distance to the destination, e.g. "0.9 km". */
101
+ remainDistance: string
102
+ /** Arrival time, e.g. "18:18". */
103
+ etaTime: string
104
+ /** Current speed, e.g. "5 km/h". */
105
+ speed: string
106
+ }
107
+
108
+ /**
109
+ * "sent" = forwarded to the device. "unsupported" = no native nav UI on this
110
+ * device / no active trip — callers should fall back to render().
111
+ */
112
+ export interface NativeNavResult {
113
+ status: "sent" | "unsupported" | "blocked"
114
+ reason?: string
115
+ }
116
+
117
+ /**
118
+ * Outcome of a render request, resolved when the host's display arbitration
119
+ * settles it. "displayed" = accepted and sent to the device — NOT a
120
+ * render confirmation. "blocked" = another app owns the display (or the
121
+ * request failed); `reason` says why.
122
+ */
123
+ export interface RenderResult {
124
+ status: "displayed" | "blocked"
125
+ /** True when the host adjusted the scene (clamped boxes, dropped elements, degraded for the device). */
126
+ degraded?: boolean
127
+ /** Ids of elements the host dropped (budget/bounds/device limits) — dropped is never silent. */
128
+ dropped?: string[]
129
+ reason?: string
130
+ }
131
+
132
+ export class DisplayManager {
133
+ constructor(private readonly session: MiniappSession) {}
134
+
135
+ /**
136
+ * Render a whole scene of positioned elements — replace-the-frame.
137
+ *
138
+ * Each call describes everything that should be on screen; the host diffs it
139
+ * against the previous frame per device (elements with a stable `id` update
140
+ * in place; elements you stop sending are removed). There is no lifecycle to
141
+ * manage and no remove calls — `render([])` clears.
142
+ *
143
+ * Coordinates are raw pixels on the device's drawable canvas — read
144
+ * `session.capabilities.display` (populated on the "ready" event) for the
145
+ * real width/height and element budgets. Out-of-bounds boxes are clamped and
146
+ * over-budget elements are dropped tail-first; both are reported via the
147
+ * result, never silently.
148
+ *
149
+ * Awaiting is OPT-IN — a plain fire-and-forget call is fine. The returned
150
+ * promise never rejects.
151
+ *
152
+ * @example
153
+ * const result = await session.display.render([
154
+ * {type: "text", id: "stats", box: {x: 12, y: 9, w: 200, h: 28}, text: "863 m 11 min"},
155
+ * {type: "image", id: "map", box: {x: 335, y: 14, w: 150, h: 150}, data: mapPng},
156
+ * {type: "rect", box: {x: 0, y: 0, w: 500, h: 220}, style: {radius: 4}},
157
+ * ])
158
+ * if (result.degraded) console.warn("dropped:", result.dropped)
159
+ */
160
+ render(elements: RenderElement[], options: RenderOptions = {}): Promise<RenderResult> {
161
+ return this.session
162
+ .sendRequest<RenderResult>({
163
+ type: MiniappRequestType.RENDER,
164
+ view: options.view ?? "main",
165
+ elements,
166
+ durationMs: options.durationMs,
167
+ })
168
+ .catch((err) => ({
169
+ status: "blocked" as const,
170
+ reason:
171
+ typeof err === "object" && err !== null && "message" in err
172
+ ? String((err as {message: unknown}).message)
173
+ : String(err),
174
+ }))
175
+ }
176
+
177
+ /** Update the native nav UI's turn-by-turn card. */
178
+ sendNativeNavInfo(info: NativeNavInfo): Promise<NativeNavResult> {
179
+ return this.sendNativeNav({action: "info", info})
180
+ }
181
+
182
+ /**
183
+ * Push the native nav UI's live mini map (base64 BMP/PNG; the host re-encodes
184
+ * and scales to the device's slot — 176×176 on G2). Draw north-up with the
185
+ * cursor centred; the device rotates the bitmap by `rotationDeg`.
186
+ */
187
+ sendNativeNavMiniMap(data: string, options: {rotationDeg?: number; border?: boolean} = {}): Promise<NativeNavResult> {
188
+ return this.sendNativeNav({action: "mini_map", map: {data, ...options}})
189
+ }
190
+
191
+ /** Push the native nav UI's overview (full-route) map — 576×188 on G2. */
192
+ sendNativeNavOverviewMap(data: string): Promise<NativeNavResult> {
193
+ return this.sendNativeNav({action: "overview_map", map: {data}})
194
+ }
195
+
196
+ private sendNativeNav(payload: Record<string, unknown>): Promise<NativeNavResult> {
197
+ return this.session
198
+ .sendRequest<NativeNavResult>({type: MiniappRequestType.NATIVE_NAV, ...payload})
199
+ .catch((err) => ({
200
+ status: "unsupported" as const,
201
+ reason:
202
+ typeof err === "object" && err !== null && "message" in err
203
+ ? String((err as {message: unknown}).message)
204
+ : String(err),
205
+ }))
206
+ }
207
+ }