@mentra/miniapp 0.3.0-dev.0

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 (277) hide show
  1. package/README.md +259 -0
  2. package/dist/background/index.d.ts +59 -0
  3. package/dist/background/index.d.ts.map +1 -0
  4. package/dist/background/index.js +25 -0
  5. package/dist/background/index.js.map +1 -0
  6. package/dist/background/register.d.ts +38 -0
  7. package/dist/background/register.d.ts.map +1 -0
  8. package/dist/background/register.js +75 -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 +62 -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/index.d.ts +58 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +31 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/modules/actions.d.ts +54 -0
  27. package/dist/modules/actions.d.ts.map +1 -0
  28. package/dist/modules/actions.js +122 -0
  29. package/dist/modules/actions.js.map +1 -0
  30. package/dist/modules/auth.d.ts +25 -0
  31. package/dist/modules/auth.d.ts.map +1 -0
  32. package/dist/modules/auth.js +52 -0
  33. package/dist/modules/auth.js.map +1 -0
  34. package/dist/modules/base64.d.ts +16 -0
  35. package/dist/modules/base64.d.ts.map +1 -0
  36. package/dist/modules/base64.js +92 -0
  37. package/dist/modules/base64.js.map +1 -0
  38. package/dist/modules/blob.d.ts +161 -0
  39. package/dist/modules/blob.d.ts.map +1 -0
  40. package/dist/modules/blob.js +277 -0
  41. package/dist/modules/blob.js.map +1 -0
  42. package/dist/modules/camera.d.ts +137 -0
  43. package/dist/modules/camera.d.ts.map +1 -0
  44. package/dist/modules/camera.js +104 -0
  45. package/dist/modules/camera.js.map +1 -0
  46. package/dist/modules/cloud.d.ts +34 -0
  47. package/dist/modules/cloud.d.ts.map +1 -0
  48. package/dist/modules/cloud.js +70 -0
  49. package/dist/modules/cloud.js.map +1 -0
  50. package/dist/modules/dashboard.d.ts +16 -0
  51. package/dist/modules/dashboard.d.ts.map +1 -0
  52. package/dist/modules/dashboard.js +27 -0
  53. package/dist/modules/dashboard.js.map +1 -0
  54. package/dist/modules/display.d.ts +106 -0
  55. package/dist/modules/display.d.ts.map +1 -0
  56. package/dist/modules/display.js +57 -0
  57. package/dist/modules/display.js.map +1 -0
  58. package/dist/modules/events.d.ts +167 -0
  59. package/dist/modules/events.d.ts.map +1 -0
  60. package/dist/modules/events.js +123 -0
  61. package/dist/modules/events.js.map +1 -0
  62. package/dist/modules/glasses.d.ts +28 -0
  63. package/dist/modules/glasses.d.ts.map +1 -0
  64. package/dist/modules/glasses.js +39 -0
  65. package/dist/modules/glasses.js.map +1 -0
  66. package/dist/modules/heading.d.ts +16 -0
  67. package/dist/modules/heading.d.ts.map +1 -0
  68. package/dist/modules/heading.js +20 -0
  69. package/dist/modules/heading.js.map +1 -0
  70. package/dist/modules/imu.d.ts +31 -0
  71. package/dist/modules/imu.d.ts.map +1 -0
  72. package/dist/modules/imu.js +40 -0
  73. package/dist/modules/imu.js.map +1 -0
  74. package/dist/modules/input.d.ts +34 -0
  75. package/dist/modules/input.d.ts.map +1 -0
  76. package/dist/modules/input.js +50 -0
  77. package/dist/modules/input.js.map +1 -0
  78. package/dist/modules/led.d.ts +35 -0
  79. package/dist/modules/led.d.ts.map +1 -0
  80. package/dist/modules/led.js +39 -0
  81. package/dist/modules/led.js.map +1 -0
  82. package/dist/modules/location.d.ts +27 -0
  83. package/dist/modules/location.d.ts.map +1 -0
  84. package/dist/modules/location.js +35 -0
  85. package/dist/modules/location.js.map +1 -0
  86. package/dist/modules/mic.d.ts +44 -0
  87. package/dist/modules/mic.d.ts.map +1 -0
  88. package/dist/modules/mic.js +67 -0
  89. package/dist/modules/mic.js.map +1 -0
  90. package/dist/modules/miniapps.d.ts +72 -0
  91. package/dist/modules/miniapps.d.ts.map +1 -0
  92. package/dist/modules/miniapps.js +48 -0
  93. package/dist/modules/miniapps.js.map +1 -0
  94. package/dist/modules/navigation.d.ts +326 -0
  95. package/dist/modules/navigation.d.ts.map +1 -0
  96. package/dist/modules/navigation.js +386 -0
  97. package/dist/modules/navigation.js.map +1 -0
  98. package/dist/modules/permissions.d.ts +58 -0
  99. package/dist/modules/permissions.d.ts.map +1 -0
  100. package/dist/modules/permissions.js +66 -0
  101. package/dist/modules/permissions.js.map +1 -0
  102. package/dist/modules/phone.d.ts +61 -0
  103. package/dist/modules/phone.d.ts.map +1 -0
  104. package/dist/modules/phone.js +94 -0
  105. package/dist/modules/phone.js.map +1 -0
  106. package/dist/modules/pivots/engine.d.ts +169 -0
  107. package/dist/modules/pivots/engine.d.ts.map +1 -0
  108. package/dist/modules/pivots/engine.js +779 -0
  109. package/dist/modules/pivots/engine.js.map +1 -0
  110. package/dist/modules/pivots/geometry.d.ts +84 -0
  111. package/dist/modules/pivots/geometry.d.ts.map +1 -0
  112. package/dist/modules/pivots/geometry.js +371 -0
  113. package/dist/modules/pivots/geometry.js.map +1 -0
  114. package/dist/modules/pivots/instructions.d.ts +139 -0
  115. package/dist/modules/pivots/instructions.d.ts.map +1 -0
  116. package/dist/modules/pivots/instructions.js +280 -0
  117. package/dist/modules/pivots/instructions.js.map +1 -0
  118. package/dist/modules/speaker.d.ts +163 -0
  119. package/dist/modules/speaker.d.ts.map +1 -0
  120. package/dist/modules/speaker.js +250 -0
  121. package/dist/modules/speaker.js.map +1 -0
  122. package/dist/modules/storage.d.ts +50 -0
  123. package/dist/modules/storage.d.ts.map +1 -0
  124. package/dist/modules/storage.js +99 -0
  125. package/dist/modules/storage.js.map +1 -0
  126. package/dist/modules/stream.d.ts +129 -0
  127. package/dist/modules/stream.d.ts.map +1 -0
  128. package/dist/modules/stream.js +52 -0
  129. package/dist/modules/stream.js.map +1 -0
  130. package/dist/modules/system.d.ts +45 -0
  131. package/dist/modules/system.d.ts.map +1 -0
  132. package/dist/modules/system.js +42 -0
  133. package/dist/modules/system.js.map +1 -0
  134. package/dist/modules/transcription.d.ts +67 -0
  135. package/dist/modules/transcription.d.ts.map +1 -0
  136. package/dist/modules/transcription.js +106 -0
  137. package/dist/modules/transcription.js.map +1 -0
  138. package/dist/modules/translation.d.ts +59 -0
  139. package/dist/modules/translation.d.ts.map +1 -0
  140. package/dist/modules/translation.js +119 -0
  141. package/dist/modules/translation.js.map +1 -0
  142. package/dist/modules/ui.d.ts +186 -0
  143. package/dist/modules/ui.d.ts.map +1 -0
  144. package/dist/modules/ui.js +293 -0
  145. package/dist/modules/ui.js.map +1 -0
  146. package/dist/protocol.d.ts +294 -0
  147. package/dist/protocol.d.ts.map +1 -0
  148. package/dist/protocol.js +320 -0
  149. package/dist/protocol.js.map +1 -0
  150. package/dist/react/MentraProvider.d.ts +33 -0
  151. package/dist/react/MentraProvider.d.ts.map +1 -0
  152. package/dist/react/MentraProvider.js +15 -0
  153. package/dist/react/MentraProvider.js.map +1 -0
  154. package/dist/react/MiniappHeader.d.ts +44 -0
  155. package/dist/react/MiniappHeader.d.ts.map +1 -0
  156. package/dist/react/MiniappHeader.js +32 -0
  157. package/dist/react/MiniappHeader.js.map +1 -0
  158. package/dist/react/index.d.ts +13 -0
  159. package/dist/react/index.d.ts.map +1 -0
  160. package/dist/react/index.js +9 -0
  161. package/dist/react/index.js.map +1 -0
  162. package/dist/react/useCapabilities.d.ts +26 -0
  163. package/dist/react/useCapabilities.d.ts.map +1 -0
  164. package/dist/react/useCapabilities.js +27 -0
  165. package/dist/react/useCapabilities.js.map +1 -0
  166. package/dist/react/useCapsuleHeaderStyle.d.ts +27 -0
  167. package/dist/react/useCapsuleHeaderStyle.d.ts.map +1 -0
  168. package/dist/react/useCapsuleHeaderStyle.js +36 -0
  169. package/dist/react/useCapsuleHeaderStyle.js.map +1 -0
  170. package/dist/react/useColorScheme.d.ts +18 -0
  171. package/dist/react/useColorScheme.d.ts.map +1 -0
  172. package/dist/react/useColorScheme.js +24 -0
  173. package/dist/react/useColorScheme.js.map +1 -0
  174. package/dist/react/useConnected.d.ts +17 -0
  175. package/dist/react/useConnected.d.ts.map +1 -0
  176. package/dist/react/useConnected.js +39 -0
  177. package/dist/react/useConnected.js.map +1 -0
  178. package/dist/react/useRpc.d.ts +29 -0
  179. package/dist/react/useRpc.d.ts.map +1 -0
  180. package/dist/react/useRpc.js +82 -0
  181. package/dist/react/useRpc.js.map +1 -0
  182. package/dist/react/useSafeArea.d.ts +21 -0
  183. package/dist/react/useSafeArea.d.ts.map +1 -0
  184. package/dist/react/useSafeArea.js +22 -0
  185. package/dist/react/useSafeArea.js.map +1 -0
  186. package/dist/session.d.ts +310 -0
  187. package/dist/session.d.ts.map +1 -0
  188. package/dist/session.js +653 -0
  189. package/dist/session.js.map +1 -0
  190. package/dist/sub-path-types.test-d.d.ts +28 -0
  191. package/dist/sub-path-types.test-d.d.ts.map +1 -0
  192. package/dist/sub-path-types.test-d.js +37 -0
  193. package/dist/sub-path-types.test-d.js.map +1 -0
  194. package/dist/transport/auto.d.ts +35 -0
  195. package/dist/transport/auto.d.ts.map +1 -0
  196. package/dist/transport/auto.js +125 -0
  197. package/dist/transport/auto.js.map +1 -0
  198. package/dist/transport/dispatch.d.ts +43 -0
  199. package/dist/transport/dispatch.d.ts.map +1 -0
  200. package/dist/transport/dispatch.js +91 -0
  201. package/dist/transport/dispatch.js.map +1 -0
  202. package/dist/transport/local-socket.d.ts +28 -0
  203. package/dist/transport/local-socket.d.ts.map +1 -0
  204. package/dist/transport/local-socket.js +80 -0
  205. package/dist/transport/local-socket.js.map +1 -0
  206. package/dist/transport/mock.d.ts +54 -0
  207. package/dist/transport/mock.d.ts.map +1 -0
  208. package/dist/transport/mock.js +205 -0
  209. package/dist/transport/mock.js.map +1 -0
  210. package/dist/transport/postmessage.d.ts +36 -0
  211. package/dist/transport/postmessage.d.ts.map +1 -0
  212. package/dist/transport/postmessage.js +78 -0
  213. package/dist/transport/postmessage.js.map +1 -0
  214. package/dist/transport/types.d.ts +25 -0
  215. package/dist/transport/types.d.ts.map +1 -0
  216. package/dist/transport/types.js +11 -0
  217. package/dist/transport/types.js.map +1 -0
  218. package/dist/ui/index.d.ts +98 -0
  219. package/dist/ui/index.d.ts.map +1 -0
  220. package/dist/ui/index.js +32 -0
  221. package/dist/ui/index.js.map +1 -0
  222. package/package.json +75 -0
  223. package/src/background/index.ts +152 -0
  224. package/src/background/register.ts +85 -0
  225. package/src/dev-reload.ts +60 -0
  226. package/src/envelope.ts +54 -0
  227. package/src/globals.ts +62 -0
  228. package/src/index.ts +171 -0
  229. package/src/modules/actions.ts +156 -0
  230. package/src/modules/auth.ts +68 -0
  231. package/src/modules/base64.ts +87 -0
  232. package/src/modules/blob.ts +341 -0
  233. package/src/modules/camera.ts +196 -0
  234. package/src/modules/cloud.ts +94 -0
  235. package/src/modules/dashboard.ts +31 -0
  236. package/src/modules/display.ts +122 -0
  237. package/src/modules/events.ts +264 -0
  238. package/src/modules/glasses.ts +44 -0
  239. package/src/modules/heading.ts +23 -0
  240. package/src/modules/imu.ts +44 -0
  241. package/src/modules/input.ts +71 -0
  242. package/src/modules/led.ts +61 -0
  243. package/src/modules/location.ts +39 -0
  244. package/src/modules/mic.ts +74 -0
  245. package/src/modules/miniapps.ts +90 -0
  246. package/src/modules/navigation.ts +670 -0
  247. package/src/modules/permissions.ts +84 -0
  248. package/src/modules/phone.ts +120 -0
  249. package/src/modules/pivots/engine.ts +863 -0
  250. package/src/modules/pivots/geometry.ts +409 -0
  251. package/src/modules/pivots/instructions.ts +343 -0
  252. package/src/modules/speaker.ts +328 -0
  253. package/src/modules/storage.ts +109 -0
  254. package/src/modules/stream.ts +163 -0
  255. package/src/modules/system.ts +76 -0
  256. package/src/modules/transcription.ts +128 -0
  257. package/src/modules/translation.ts +149 -0
  258. package/src/modules/ui.ts +498 -0
  259. package/src/protocol.ts +371 -0
  260. package/src/react/MentraProvider.tsx +50 -0
  261. package/src/react/MiniappHeader.tsx +122 -0
  262. package/src/react/index.ts +12 -0
  263. package/src/react/useCapabilities.ts +29 -0
  264. package/src/react/useCapsuleHeaderStyle.ts +54 -0
  265. package/src/react/useColorScheme.ts +26 -0
  266. package/src/react/useConnected.ts +47 -0
  267. package/src/react/useRpc.ts +106 -0
  268. package/src/react/useSafeArea.ts +40 -0
  269. package/src/session.ts +878 -0
  270. package/src/sub-path-types.test-d.ts +49 -0
  271. package/src/transport/auto.ts +143 -0
  272. package/src/transport/dispatch.ts +106 -0
  273. package/src/transport/local-socket.ts +93 -0
  274. package/src/transport/mock.ts +248 -0
  275. package/src/transport/postmessage.ts +95 -0
  276. package/src/transport/types.ts +26 -0
  277. package/src/ui/index.ts +122 -0
@@ -0,0 +1,196 @@
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
+ compress?: "none" | "low" | "medium" | "high"
37
+ sound?: boolean
38
+ saveToGallery?: boolean
39
+ /**
40
+ * Manual shutter / exposure time in nanoseconds. Omit (or pass undefined)
41
+ * to let the glasses auto-expose. Honored only on cameras that support
42
+ * manual exposure; ignored otherwise.
43
+ */
44
+ exposureTimeNs?: number
45
+ }
46
+
47
+ export interface PhotoTaken {
48
+ /** Request id that correlates ASG status, upload, and phone/cloud logs. */
49
+ requestId: string
50
+ photoUrl: string
51
+ mimeType: string
52
+ size: number
53
+ }
54
+
55
+ export interface WarmUpCameraOptions {
56
+ size?: "low" | "medium" | "high" | "max"
57
+ exposureTimeNs?: number
58
+ durationMs?: number
59
+ }
60
+
61
+ export interface StartVideoRecordingOptions {
62
+ /** Video width in pixels. Omit to use the device's saved button-video default. */
63
+ width?: number
64
+ /** Video height in pixels. Omit to use the device's saved button-video default. */
65
+ height?: number
66
+ /**
67
+ * Frames per second. Lower frame rates keep the glasses cooler and produce
68
+ * smaller files — useful for long recordings where smooth motion isn't needed.
69
+ * Omit to use the device default.
70
+ */
71
+ fps?: number
72
+ /** Play start/stop capture sounds. Defaults to true. */
73
+ sound?: boolean
74
+ /** Keep the recording on the glasses after it finishes. Defaults to false. */
75
+ save?: boolean
76
+ }
77
+
78
+ export interface VideoRecordingStarted {
79
+ /** Identifier for this recording session; pass to {@link stopVideoRecording}. */
80
+ recordingId: string
81
+ }
82
+
83
+ export interface StopVideoRecordingOptions {
84
+ /**
85
+ * Upload the finished recording to this URL with a multipart POST (the file is
86
+ * the `video` part). The glasses upload it directly. Omit to keep the recording
87
+ * on the glasses instead.
88
+ */
89
+ uploadUrl?: string
90
+ /** Bearer token sent as the `Authorization` header on the upload, if your endpoint needs one. */
91
+ uploadAuthToken?: string
92
+ }
93
+
94
+ export class CameraModule {
95
+ constructor(private readonly session: MiniappSession) {}
96
+
97
+ /** True iff `CAMERA` is declared in the miniapp's manifest. */
98
+ get hasPermission(): boolean {
99
+ return this.session._hasManifestPermission("CAMERA")
100
+ }
101
+
102
+ /**
103
+ * Apply camera FOV/ROI settings on the glasses.
104
+ *
105
+ * Resolves after the ASG client reports that the setting was applied to camera
106
+ * hardware after the restart cooldown. Requires CAMERA permission declared in miniapp.json.
107
+ */
108
+ async setFov(request: CameraFovRequest): Promise<CameraFovResult> {
109
+ return this.session.sendRequest<CameraFovResult>({
110
+ type: MiniappRequestType.CAMERA_FOV,
111
+ ...request,
112
+ })
113
+ }
114
+
115
+ /**
116
+ * Take a photo via the glasses camera. Returns a URL to the captured image.
117
+ * Requires CAMERA permission declared in miniapp.json.
118
+ *
119
+ * The photo is uploaded to cloud storage; the returned URL is a short-TTL
120
+ * (~30 minute) signed download URL. If the glasses don't have a camera,
121
+ * the phone-side handler rejects with an error. Check
122
+ * `session.capabilities.hasCamera` before calling.
123
+ */
124
+ async takePhoto(options: TakePhotoOptions = {}): Promise<PhotoTaken> {
125
+ return this.session.sendRequest<PhotoTaken>({
126
+ type: MiniappRequestType.PHOTO,
127
+ size: options.size ?? "medium",
128
+ mode: options.mode ?? "photo",
129
+ compress: options.compress ?? "none",
130
+ sound: options.sound ?? true,
131
+ saveToGallery: options.saveToGallery ?? false,
132
+ exposureTimeNs: options.exposureTimeNs,
133
+ })
134
+ }
135
+
136
+ /**
137
+ * Pre-warm the glasses camera so the next takePhoto() is near-instant.
138
+ * The camera stays warm for ~durationMs (default 15s); call warmUp() again to
139
+ * extend it. Warm with the same `size` you'll capture with — a mismatched size
140
+ * forces the camera to reconfigure and loses the speedup. Requires CAMERA
141
+ * permission in miniapp.json. Resolves once the camera reports ready.
142
+ *
143
+ * Warm-ups are serialized: only one runs at a time and none may start while a
144
+ * photo is being captured. If the camera is busy (a capture is in flight, or
145
+ * another warm-up is still opening) this rejects with `camera_busy` — retry
146
+ * shortly, or just call takePhoto() (it works regardless, only slower). The
147
+ * normal warmUp() → takePhoto() sequence never hits this.
148
+ */
149
+ async warmUp(options: WarmUpCameraOptions = {}): Promise<void> {
150
+ await this.session.sendRequest<void>({
151
+ type: MiniappRequestType.CAMERA_WARM_UP,
152
+ size: options.size ?? "medium",
153
+ exposureTimeNs: options.exposureTimeNs,
154
+ durationMs: options.durationMs ?? 15000,
155
+ })
156
+ }
157
+
158
+ /**
159
+ * Start recording video on the glasses camera. Returns a `recordingId` to pass
160
+ * to {@link stopVideoRecording} after the glasses report that recording
161
+ * started. Requires CAMERA permission declared in miniapp.json. Check
162
+ * `session.capabilities.hasCamera` before calling.
163
+ *
164
+ * Resolution and frame rate are optional — omit them to use the device's saved
165
+ * button-video settings. Lowering `fps` (e.g. to 5) keeps the glasses cooler
166
+ * during long recordings.
167
+ */
168
+ async startVideoRecording(options: StartVideoRecordingOptions = {}): Promise<VideoRecordingStarted> {
169
+ return this.session.sendRequest<VideoRecordingStarted>({
170
+ type: MiniappRequestType.VIDEO_RECORDING_START,
171
+ width: options.width,
172
+ height: options.height,
173
+ fps: options.fps,
174
+ sound: options.sound ?? true,
175
+ save: options.save ?? false,
176
+ })
177
+ }
178
+
179
+ /**
180
+ * Stop an in-progress video recording started with {@link startVideoRecording}.
181
+ *
182
+ * By default the recording stays on the glasses. Pass `uploadUrl` to have the
183
+ * glasses upload the finished clip to your own endpoint.
184
+ *
185
+ * @param recordingId The id returned from `startVideoRecording`.
186
+ * @param options Optional upload destination for the finished recording.
187
+ */
188
+ async stopVideoRecording(recordingId: string, options: StopVideoRecordingOptions = {}): Promise<void> {
189
+ await this.session.sendRequest<void>({
190
+ type: MiniappRequestType.VIDEO_RECORDING_STOP,
191
+ recordingId,
192
+ uploadUrl: options.uploadUrl,
193
+ uploadAuthToken: options.uploadAuthToken,
194
+ })
195
+ }
196
+ }
@@ -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,31 @@
1
+ /**
2
+ * @fileoverview DashboardAPI — noop surface in v1.
3
+ *
4
+ * The cloud DashboardManager owns widget rendering in OS-ranked
5
+ * rotation. Keeping the API shape so miniapps compile, but calls are
6
+ * noop + console.warn.
7
+ */
8
+
9
+ import {MiniappRequestType} from "../protocol"
10
+ import {MiniappSession} from "../session"
11
+
12
+ export type DashboardMode = "main" | "expanded" | "always_on"
13
+
14
+ export class DashboardAPI {
15
+ private warned = false
16
+
17
+ constructor(private readonly session: MiniappSession) {}
18
+
19
+ setContent(mode: DashboardMode, content: string): void {
20
+ if (!this.warned) {
21
+ console.warn("[@mentra/miniapp] dashboard.setContent() is deferred in v1.")
22
+ this.warned = true
23
+ }
24
+ // Still forward so the phone can log/ignore consistently.
25
+ this.session.sendOneShot({
26
+ type: MiniappRequestType.DASHBOARD_CONTENT_UPDATE,
27
+ mode,
28
+ content,
29
+ })
30
+ }
31
+ }
@@ -0,0 +1,122 @@
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
+ /** Pixel-space bounding box on the device's drawable canvas (see `capabilities.display`). */
24
+ export interface RenderBox {
25
+ x: number
26
+ y: number
27
+ w: number
28
+ h: number
29
+ }
30
+
31
+ export interface RenderTextStyle {
32
+ /** Border width in px (0/absent = none). */
33
+ border?: number
34
+ /** Border corner radius in px. */
35
+ radius?: number
36
+ /** What happens to text that doesn't fit the box after wrapping. Default "clip". */
37
+ overflow?: "clip" | "ellipsis"
38
+ /** Line-break policy for wrapping (host wraps; the box carries pre-wrapped text). */
39
+ breakMode?: DisplayBreakMode
40
+ }
41
+
42
+ export interface RenderRectStyle {
43
+ border?: number
44
+ radius?: number
45
+ }
46
+
47
+ /**
48
+ * One element of a rendered scene. `id` is optional but recommended for
49
+ * anything that updates over time: elements with a stable id update in place on
50
+ * the glasses (no flicker); the host matches unnamed elements by geometry.
51
+ */
52
+ export type RenderElement =
53
+ | {type: "text"; id?: string; box: RenderBox; text: string; style?: RenderTextStyle}
54
+ | {type: "image"; id?: string; box: RenderBox; data: string}
55
+ | {type: "rect"; id?: string; box: RenderBox; style?: RenderRectStyle}
56
+
57
+ export interface RenderOptions {
58
+ view?: ViewType
59
+ /** Auto-clear after this many ms (same semantics as legacy display options). */
60
+ durationMs?: number
61
+ }
62
+
63
+ /**
64
+ * Outcome of a render request, resolved when the host's display arbitration
65
+ * settles it. "displayed" = accepted and sent to the device — NOT a
66
+ * render confirmation. "blocked" = another app owns the display (or the
67
+ * request failed); `reason` says why.
68
+ */
69
+ export interface RenderResult {
70
+ status: "displayed" | "blocked"
71
+ /** True when the host adjusted the scene (clamped boxes, dropped elements, degraded for the device). */
72
+ degraded?: boolean
73
+ /** Ids of elements the host dropped (budget/bounds/device limits) — dropped is never silent. */
74
+ dropped?: string[]
75
+ reason?: string
76
+ }
77
+
78
+ export class DisplayManager {
79
+ constructor(private readonly session: MiniappSession) {}
80
+
81
+ /**
82
+ * Render a whole scene of positioned elements — replace-the-frame.
83
+ *
84
+ * Each call describes everything that should be on screen; the host diffs it
85
+ * against the previous frame per device (elements with a stable `id` update
86
+ * in place; elements you stop sending are removed). There is no lifecycle to
87
+ * manage and no remove calls — `render([])` clears.
88
+ *
89
+ * Coordinates are raw pixels on the device's drawable canvas — read
90
+ * `session.capabilities.display` (populated on the "ready" event) for the
91
+ * real width/height and element budgets. Out-of-bounds boxes are clamped and
92
+ * over-budget elements are dropped tail-first; both are reported via the
93
+ * result, never silently.
94
+ *
95
+ * Awaiting is OPT-IN — a plain fire-and-forget call is fine. The returned
96
+ * promise never rejects.
97
+ *
98
+ * @example
99
+ * const result = await session.display.render([
100
+ * {type: "text", id: "stats", box: {x: 12, y: 9, w: 200, h: 28}, text: "863 m 11 min"},
101
+ * {type: "image", id: "map", box: {x: 335, y: 14, w: 150, h: 150}, data: mapPng},
102
+ * {type: "rect", box: {x: 0, y: 0, w: 500, h: 220}, style: {radius: 4}},
103
+ * ])
104
+ * if (result.degraded) console.warn("dropped:", result.dropped)
105
+ */
106
+ render(elements: RenderElement[], options: RenderOptions = {}): Promise<RenderResult> {
107
+ return this.session
108
+ .sendRequest<RenderResult>({
109
+ type: MiniappRequestType.RENDER,
110
+ view: options.view ?? "main",
111
+ elements,
112
+ durationMs: options.durationMs,
113
+ })
114
+ .catch((err) => ({
115
+ status: "blocked" as const,
116
+ reason:
117
+ typeof err === "object" && err !== null && "message" in err
118
+ ? String((err as {message: unknown}).message)
119
+ : String(err),
120
+ }))
121
+ }
122
+ }