@glassly/miniapp 0.3.0-dev.1 → 0.3.0-dev.460

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 (255) hide show
  1. package/README.md +219 -74
  2. package/dist/background/index.d.ts +14 -8
  3. package/dist/background/index.d.ts.map +1 -1
  4. package/dist/background/index.js +4 -0
  5. package/dist/background/index.js.map +1 -1
  6. package/dist/background/register.d.ts +1 -1
  7. package/dist/background/register.js +1 -1
  8. package/dist/display/index.d.ts +23 -0
  9. package/dist/display/index.d.ts.map +1 -0
  10. package/dist/display/index.js +17 -0
  11. package/dist/display/index.js.map +1 -0
  12. package/dist/display/measurer/TextMeasurer.d.ts +160 -0
  13. package/dist/display/measurer/TextMeasurer.d.ts.map +1 -0
  14. package/dist/display/measurer/TextMeasurer.js +272 -0
  15. package/dist/display/measurer/TextMeasurer.js.map +1 -0
  16. package/dist/display/measurer/index.d.ts +10 -0
  17. package/dist/display/measurer/index.d.ts.map +1 -0
  18. package/dist/display/measurer/index.js +11 -0
  19. package/dist/display/measurer/index.js.map +1 -0
  20. package/dist/display/measurer/script-detection.d.ts +53 -0
  21. package/dist/display/measurer/script-detection.d.ts.map +1 -0
  22. package/dist/display/measurer/script-detection.js +184 -0
  23. package/dist/display/measurer/script-detection.js.map +1 -0
  24. package/dist/display/pixelArt.d.ts +22 -0
  25. package/dist/display/pixelArt.d.ts.map +1 -0
  26. package/dist/display/pixelArt.js +126 -0
  27. package/dist/display/pixelArt.js.map +1 -0
  28. package/dist/display/profiles/g1.d.ts +33 -0
  29. package/dist/display/profiles/g1.d.ts.map +1 -0
  30. package/dist/display/profiles/g1.js +214 -0
  31. package/dist/display/profiles/g1.js.map +1 -0
  32. package/dist/display/profiles/g2.d.ts +13 -0
  33. package/dist/display/profiles/g2.d.ts.map +1 -0
  34. package/dist/display/profiles/g2.js +34 -0
  35. package/dist/display/profiles/g2.js.map +1 -0
  36. package/dist/display/profiles/index.d.ts +12 -0
  37. package/dist/display/profiles/index.d.ts.map +1 -0
  38. package/dist/display/profiles/index.js +15 -0
  39. package/dist/display/profiles/index.js.map +1 -0
  40. package/dist/display/profiles/nex.d.ts +10 -0
  41. package/dist/display/profiles/nex.d.ts.map +1 -0
  42. package/dist/display/profiles/nex.js +150 -0
  43. package/dist/display/profiles/nex.js.map +1 -0
  44. package/dist/display/profiles/types.d.ts +103 -0
  45. package/dist/display/profiles/types.d.ts.map +1 -0
  46. package/dist/display/profiles/types.js +2 -0
  47. package/dist/display/profiles/types.js.map +1 -0
  48. package/dist/display/profiles/z100.d.ts +33 -0
  49. package/dist/display/profiles/z100.d.ts.map +1 -0
  50. package/dist/display/profiles/z100.js +182 -0
  51. package/dist/display/profiles/z100.js.map +1 -0
  52. package/dist/display/scene/types.d.ts +292 -0
  53. package/dist/display/scene/types.d.ts.map +1 -0
  54. package/dist/display/scene/types.js +0 -0
  55. package/dist/display/scene/types.js.map +1 -0
  56. package/dist/display/wrapper/TextWrapper.d.ts +102 -0
  57. package/dist/display/wrapper/TextWrapper.d.ts.map +1 -0
  58. package/dist/display/wrapper/TextWrapper.js +469 -0
  59. package/dist/display/wrapper/TextWrapper.js.map +1 -0
  60. package/dist/display/wrapper/index.d.ts +12 -0
  61. package/dist/display/wrapper/index.d.ts.map +1 -0
  62. package/dist/display/wrapper/index.js +12 -0
  63. package/dist/display/wrapper/index.js.map +1 -0
  64. package/dist/display/wrapper/types.d.ts +75 -0
  65. package/dist/display/wrapper/types.d.ts.map +1 -0
  66. package/dist/display/wrapper/types.js +11 -0
  67. package/dist/display/wrapper/types.js.map +1 -0
  68. package/dist/globals.d.ts +5 -5
  69. package/dist/globals.d.ts.map +1 -1
  70. package/dist/globals.js +1 -1
  71. package/dist/globals.js.map +1 -1
  72. package/dist/hardware.d.ts +4 -18
  73. package/dist/hardware.d.ts.map +1 -1
  74. package/dist/hardware.js +2 -13
  75. package/dist/hardware.js.map +1 -1
  76. package/dist/index.d.ts +16 -10
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +3 -1
  79. package/dist/index.js.map +1 -1
  80. package/dist/knownApps.d.ts +26 -0
  81. package/dist/knownApps.d.ts.map +1 -0
  82. package/dist/knownApps.js +140 -0
  83. package/dist/knownApps.js.map +1 -0
  84. package/dist/mirror/canvas.d.ts +22 -0
  85. package/dist/mirror/canvas.d.ts.map +1 -0
  86. package/dist/mirror/canvas.js +311 -0
  87. package/dist/mirror/canvas.js.map +1 -0
  88. package/dist/mirror/geometry.d.ts +69 -0
  89. package/dist/mirror/geometry.d.ts.map +1 -0
  90. package/dist/mirror/geometry.js +75 -0
  91. package/dist/mirror/geometry.js.map +1 -0
  92. package/dist/mirror/index.d.ts +3 -0
  93. package/dist/mirror/index.d.ts.map +1 -0
  94. package/dist/mirror/index.js +2 -0
  95. package/dist/mirror/index.js.map +1 -0
  96. package/dist/mirror/types.d.ts +40 -0
  97. package/dist/mirror/types.d.ts.map +1 -0
  98. package/dist/mirror/types.js +2 -0
  99. package/dist/mirror/types.js.map +1 -0
  100. package/dist/modules/auth.d.ts +1 -1
  101. package/dist/modules/auth.d.ts.map +1 -1
  102. package/dist/modules/auth.js +2 -2
  103. package/dist/modules/auth.js.map +1 -1
  104. package/dist/modules/compass.d.ts +47 -0
  105. package/dist/modules/compass.d.ts.map +1 -0
  106. package/dist/modules/compass.js +39 -0
  107. package/dist/modules/compass.js.map +1 -0
  108. package/dist/modules/display.d.ts +192 -8
  109. package/dist/modules/display.d.ts.map +1 -1
  110. package/dist/modules/display.js +40 -3
  111. package/dist/modules/display.js.map +1 -1
  112. package/dist/modules/events.d.ts +30 -2
  113. package/dist/modules/events.d.ts.map +1 -1
  114. package/dist/modules/events.js.map +1 -1
  115. package/dist/modules/input.d.ts +40 -1
  116. package/dist/modules/input.d.ts.map +1 -1
  117. package/dist/modules/input.js +45 -1
  118. package/dist/modules/input.js.map +1 -1
  119. package/dist/modules/latest-render-scheduler.d.ts +19 -0
  120. package/dist/modules/latest-render-scheduler.d.ts.map +1 -0
  121. package/dist/modules/latest-render-scheduler.js +54 -0
  122. package/dist/modules/latest-render-scheduler.js.map +1 -0
  123. package/dist/modules/lightSensor.d.ts +44 -0
  124. package/dist/modules/lightSensor.d.ts.map +1 -0
  125. package/dist/modules/lightSensor.js +33 -0
  126. package/dist/modules/lightSensor.js.map +1 -0
  127. package/dist/modules/mic.d.ts +3 -2
  128. package/dist/modules/mic.d.ts.map +1 -1
  129. package/dist/modules/mic.js +3 -2
  130. package/dist/modules/mic.js.map +1 -1
  131. package/dist/modules/miniapps.d.ts +2 -0
  132. package/dist/modules/miniapps.d.ts.map +1 -1
  133. package/dist/modules/miniapps.js.map +1 -1
  134. package/dist/modules/mirror.d.ts +16 -0
  135. package/dist/modules/mirror.d.ts.map +1 -0
  136. package/dist/modules/mirror.js +40 -0
  137. package/dist/modules/mirror.js.map +1 -0
  138. package/dist/modules/mono-text-rows.d.ts +20 -0
  139. package/dist/modules/mono-text-rows.d.ts.map +1 -0
  140. package/dist/modules/mono-text-rows.js +26 -0
  141. package/dist/modules/mono-text-rows.js.map +1 -0
  142. package/dist/modules/navigation.d.ts.map +1 -1
  143. package/dist/modules/navigation.js +21 -14
  144. package/dist/modules/navigation.js.map +1 -1
  145. package/dist/modules/phone.d.ts +26 -1
  146. package/dist/modules/phone.d.ts.map +1 -1
  147. package/dist/modules/phone.js +28 -0
  148. package/dist/modules/phone.js.map +1 -1
  149. package/dist/modules/settings.d.ts +45 -0
  150. package/dist/modules/settings.d.ts.map +1 -0
  151. package/dist/modules/settings.js +54 -0
  152. package/dist/modules/settings.js.map +1 -0
  153. package/dist/modules/system.d.ts +21 -0
  154. package/dist/modules/system.d.ts.map +1 -1
  155. package/dist/modules/system.js +12 -0
  156. package/dist/modules/system.js.map +1 -1
  157. package/dist/modules/transcription.d.ts +3 -2
  158. package/dist/modules/transcription.d.ts.map +1 -1
  159. package/dist/modules/transcription.js +3 -2
  160. package/dist/modules/transcription.js.map +1 -1
  161. package/dist/protocol.d.ts +65 -5
  162. package/dist/protocol.d.ts.map +1 -1
  163. package/dist/protocol.js +66 -5
  164. package/dist/protocol.js.map +1 -1
  165. package/dist/react/MirrorCanvas.d.ts +10 -0
  166. package/dist/react/MirrorCanvas.d.ts.map +1 -0
  167. package/dist/react/MirrorCanvas.js +23 -0
  168. package/dist/react/MirrorCanvas.js.map +1 -0
  169. package/dist/react/index.d.ts +2 -0
  170. package/dist/react/index.d.ts.map +1 -1
  171. package/dist/react/index.js +1 -0
  172. package/dist/react/index.js.map +1 -1
  173. package/dist/react/useColorScheme.d.ts +2 -3
  174. package/dist/react/useColorScheme.d.ts.map +1 -1
  175. package/dist/react/useColorScheme.js +15 -8
  176. package/dist/react/useColorScheme.js.map +1 -1
  177. package/dist/react/useRpc.js +2 -2
  178. package/dist/react/useRpc.js.map +1 -1
  179. package/dist/react/useSafeArea.d.ts.map +1 -1
  180. package/dist/react/useSafeArea.js +2 -2
  181. package/dist/react/useSafeArea.js.map +1 -1
  182. package/dist/session.d.ts +50 -4
  183. package/dist/session.d.ts.map +1 -1
  184. package/dist/session.js +35 -6
  185. package/dist/session.js.map +1 -1
  186. package/dist/transport/auto.d.ts +2 -2
  187. package/dist/transport/auto.js +2 -2
  188. package/dist/transport/dispatch.d.ts +1 -1
  189. package/dist/transport/dispatch.js +1 -1
  190. package/dist/transport/mock.d.ts +4 -4
  191. package/dist/transport/mock.d.ts.map +1 -1
  192. package/dist/transport/mock.js +31 -7
  193. package/dist/transport/mock.js.map +1 -1
  194. package/dist/ui/index.d.ts +2 -0
  195. package/dist/ui/index.d.ts.map +1 -1
  196. package/dist/ui/index.js +1 -0
  197. package/dist/ui/index.js.map +1 -1
  198. package/package.json +19 -3
  199. package/src/background/index.ts +26 -4
  200. package/src/background/register.ts +1 -1
  201. package/src/display/index.ts +83 -0
  202. package/src/display/measurer/TextMeasurer.ts +346 -0
  203. package/src/display/measurer/index.ts +21 -0
  204. package/src/display/measurer/script-detection.ts +218 -0
  205. package/src/display/pixelArt.ts +141 -0
  206. package/src/display/profiles/g1.ts +237 -0
  207. package/src/display/profiles/g2.ts +39 -0
  208. package/src/display/profiles/index.ts +28 -0
  209. package/src/display/profiles/nex.ts +166 -0
  210. package/src/display/profiles/types.ts +132 -0
  211. package/src/display/profiles/z100.ts +198 -0
  212. package/src/display/scene/types.ts +335 -0
  213. package/src/display/wrapper/TextWrapper.ts +546 -0
  214. package/src/display/wrapper/index.ts +15 -0
  215. package/src/display/wrapper/types.ts +102 -0
  216. package/src/globals.ts +5 -5
  217. package/src/hardware.ts +6 -21
  218. package/src/index.ts +34 -6
  219. package/src/knownApps.ts +170 -0
  220. package/src/mirror/canvas.ts +322 -0
  221. package/src/mirror/geometry.ts +128 -0
  222. package/src/mirror/index.ts +2 -0
  223. package/src/mirror/types.ts +38 -0
  224. package/src/modules/auth.ts +2 -2
  225. package/src/modules/compass.ts +69 -0
  226. package/src/modules/display.ts +222 -12
  227. package/src/modules/events.ts +27 -1
  228. package/src/modules/input.ts +55 -2
  229. package/src/modules/latest-render-scheduler.ts +55 -0
  230. package/src/modules/lightSensor.ts +61 -0
  231. package/src/modules/mic.ts +3 -2
  232. package/src/modules/miniapps.ts +2 -0
  233. package/src/modules/mirror.ts +42 -0
  234. package/src/modules/mono-text-rows.ts +30 -0
  235. package/src/modules/navigation.ts +20 -14
  236. package/src/modules/phone.ts +40 -1
  237. package/src/modules/settings.ts +64 -0
  238. package/src/modules/system.ts +30 -1
  239. package/src/modules/transcription.ts +3 -2
  240. package/src/protocol.ts +72 -5
  241. package/src/react/MirrorCanvas.tsx +35 -0
  242. package/src/react/index.ts +3 -0
  243. package/src/react/useColorScheme.ts +17 -8
  244. package/src/react/useRpc.ts +2 -2
  245. package/src/react/useSafeArea.ts +2 -2
  246. package/src/session.ts +78 -8
  247. package/src/transport/auto.ts +2 -2
  248. package/src/transport/dispatch.ts +1 -1
  249. package/src/transport/mock.ts +42 -11
  250. package/src/ui/index.ts +3 -0
  251. package/dist/modules/heading.d.ts +0 -16
  252. package/dist/modules/heading.d.ts.map +0 -1
  253. package/dist/modules/heading.js +0 -20
  254. package/dist/modules/heading.js.map +0 -1
  255. package/src/modules/heading.ts +0 -23
package/src/hardware.ts CHANGED
@@ -1,14 +1,4 @@
1
- /**
2
- * Hardware requirement types — canonical home.
3
- *
4
- * Moved verbatim from the legacy `@glassly/types` package so miniapp authors
5
- * can type their miniapp.json manifest without any dependency on legacy cloud.
6
- * `@glassly/engine` re-exports these; do not fork copies elsewhere.
7
- */
8
-
9
- /**
10
- * Hardware component types that apps can require
11
- */
1
+ /** Hardware components that a miniapp can declare in its manifest. */
12
2
  export enum HardwareType {
13
3
  CAMERA = "CAMERA",
14
4
  DISPLAY = "DISPLAY",
@@ -21,20 +11,15 @@ export enum HardwareType {
21
11
  EXIST = "EXIST",
22
12
  }
23
13
 
24
- /**
25
- * Levels of hardware requirements
26
- */
14
+ /** Whether a missing component prevents the miniapp from running. */
27
15
  export enum HardwareRequirementLevel {
28
- REQUIRED = "REQUIRED", // App cannot function without this hardware
29
- OPTIONAL = "OPTIONAL", // App has enhanced features with this hardware
16
+ REQUIRED = "REQUIRED",
17
+ OPTIONAL = "OPTIONAL",
30
18
  }
31
19
 
32
- /**
33
- * Hardware requirement for an app
34
- * Specifies what hardware components an app needs
35
- */
20
+ /** One hardware requirement from a miniapp manifest. */
36
21
  export interface HardwareRequirement {
37
22
  type: HardwareType
38
23
  level: HardwareRequirementLevel
39
- description?: string // Why this hardware is needed
24
+ description?: string
40
25
  }
package/src/index.ts CHANGED
@@ -16,14 +16,19 @@ import {installDevReloadListenerIfDevMode} from "./dev-reload"
16
16
  installDevReloadListenerIfDevMode()
17
17
 
18
18
  export {MiniappSession, NotConnectedError} from "./session"
19
+ export {monoTextRows} from "./modules/mono-text-rows"
20
+ export {LatestRenderScheduler} from "./modules/latest-render-scheduler"
19
21
  export type {
20
22
  AuthUpdatePayload,
21
23
  ConnectAckPayload,
22
24
  DisplayCapabilities,
23
25
  GlassesCapabilities,
24
26
  MiniappAuthState,
27
+ MiniappLaunchSource,
28
+ MiniappPreferences,
25
29
  MiniappRequestError,
26
30
  MiniappSessionOptions,
31
+ MiniappUnitSystem,
27
32
  MiniappVisibility,
28
33
  } from "./session"
29
34
  export type {AuthFetchOptions, AuthModule} from "./modules/auth"
@@ -31,8 +36,8 @@ export type {AuthFetchOptions, AuthModule} from "./modules/auth"
31
36
  export {makeRequestId, parseEnvelope, serializeEnvelope} from "./envelope"
32
37
  export type {MiniappEnvelope} from "./envelope"
33
38
 
34
- export {getGlasslyGlobals} from "./globals"
35
- export type {GlasslyGlobals, MiniappCapsuleMenuRect, MiniappColorScheme, MiniappSafeAreaInsets} from "./globals"
39
+ export {getHostGlobals} from "./globals"
40
+ export type {HostGlobals, MiniappCapsuleMenuRect, MiniappColorScheme, MiniappSafeAreaInsets} from "./globals"
36
41
 
37
42
  export {MiniappErrorCode, MiniappRequestType, MiniappResponseType, MiniappStreamType} from "./protocol"
38
43
  export {CLOUD_STATUS_STREAM} from "./modules/cloud"
@@ -73,10 +78,18 @@ export type {
73
78
  RenderBox,
74
79
  RenderElement,
75
80
  RenderOptions,
81
+ DisplayViewChange,
82
+ DisplayViewChangeReason,
83
+ ShowViewResult,
76
84
  RenderRectStyle,
77
85
  RenderResult,
78
86
  RenderTextStyle,
79
87
  ViewType,
88
+ RenderPoint,
89
+ RenderShapeStyle,
90
+ RenderStrokeStyle,
91
+ RenderEasing,
92
+ RenderTransition,
80
93
  } from "./modules/display"
81
94
  export type {
82
95
  AccelData,
@@ -86,6 +99,7 @@ export type {
86
99
  ConnectionData,
87
100
  HeadingData,
88
101
  HeadPositionData,
102
+ KeyboardEventData,
89
103
  LocationData,
90
104
  NotificationDismissedData,
91
105
  PhoneNotificationData,
@@ -96,7 +110,7 @@ export type {
96
110
  VadData,
97
111
  WifiData,
98
112
  } from "./modules/events"
99
- export type {CalendarEvent, CalendarListOptions, CalendarListResult} from "./modules/phone"
113
+ export type {CalendarEvent, CalendarListOptions, CalendarListResult, NotificationAppInfo} from "./modules/phone"
100
114
  export type {
101
115
  PlayAudioOptions,
102
116
  SpeakOptions,
@@ -139,6 +153,8 @@ export type {
139
153
  DownloadResult,
140
154
  ScanQrOptions,
141
155
  ScanQrResult,
156
+ DisplayDiagnostic,
157
+ DisplayDiagnosticsResult,
142
158
  } from "./modules/system"
143
159
  export type {MiniappInfo, MiniappActionInfo, MiniappCompatibility, ListMiniappsOptions} from "./modules/miniapps"
144
160
  export type {ActionContext, ActionDescriptor, ActionHandler, InvokeOptions} from "./modules/actions"
@@ -149,7 +165,6 @@ export type {DisplayManager} from "./modules/display"
149
165
  export type {MiniappsModule} from "./modules/miniapps"
150
166
  export type {ActionsModule} from "./modules/actions"
151
167
  export type {GlassesModule} from "./modules/glasses"
152
- export type {HeadingModule} from "./modules/heading"
153
168
  export type {
154
169
  AiModule,
155
170
  AiProvider,
@@ -164,7 +179,7 @@ export type {
164
179
  AiUsage,
165
180
  } from "./modules/ai"
166
181
  export type {ImuModule} from "./modules/imu"
167
- export type {InputModule} from "./modules/input"
182
+ export type {InputModule, GestureClaimChange} from "./modules/input"
168
183
  export type {KeysModule, ApiKeyProvider} from "./modules/keys"
169
184
  export type {LocationModule} from "./modules/location"
170
185
  export type {
@@ -207,10 +222,23 @@ export type {
207
222
  TravelMode,
208
223
  } from "./modules/navigation"
209
224
  export type {PermissionsModule, PermissionErrorEvent} from "./modules/permissions"
210
- export type {PhoneModule, PhoneNotificationsModule, PhoneCalendarModule} from "./modules/phone"
225
+ export type {PhoneModule, PhoneNotificationsModule, PhoneCalendarModule, PhoneHeadingModule} from "./modules/phone"
226
+ export type {SettingsModule, SettingsChange} from "./modules/settings"
211
227
  export type {TranscriptionModule, TranscriptionConfig, TranscriptionOptions} from "./modules/transcription"
212
228
  export type {TranslationModule} from "./modules/translation"
213
229
  export type {SpeakerModule} from "./modules/speaker"
214
230
 
215
231
  // Permission types
216
232
  export type {PermissionType, PermissionRecord} from "./session"
233
+
234
+ export type {LightSensorModule, LightSensorOptions, LightSensorData} from "./modules/lightSensor"
235
+ export type {
236
+ CompassModule,
237
+ CompassOptions,
238
+ CompassData,
239
+ CompassDiagnostics,
240
+ CompassCalibrationData,
241
+ } from "./modules/compass"
242
+
243
+ export type {MirrorModule} from "./modules/mirror"
244
+ export type {MirrorSnapshot, MirrorContent, MirrorElement} from "./mirror/types"
@@ -0,0 +1,170 @@
1
+ /**
2
+ * The apps the notification surfaces know by id: a display name for a bare
3
+ * bundle id or package name and, for the glasses card, which glyph stands in
4
+ * for the app's icon (iOS never exposes another app's icon, and one would not
5
+ * survive 24px of 4-bit gray anyway). The host seeds its notification app
6
+ * catalog from the iOS entries and fills in names on bare ids; miniapps map
7
+ * the glyph names onto their own pixel art.
8
+ *
9
+ * Ids keep their real case so the catalog can hand them back; lookups are
10
+ * case-insensitive.
11
+ */
12
+
13
+ export type AppGlyph =
14
+ | "bubble"
15
+ | "envelope"
16
+ | "calendar"
17
+ | "phone"
18
+ | "camera"
19
+ | "note"
20
+ | "pin"
21
+ | "clock"
22
+ | "play"
23
+ | "cart"
24
+ | "car"
25
+ | "check"
26
+ | "photo"
27
+ | "heart"
28
+ | "dollar"
29
+
30
+ export interface KnownApp {
31
+ /** iOS bundle id or Android package name, real case. */
32
+ id: string
33
+ name: string
34
+ /** Absent: the generic message bubble. */
35
+ glyph?: AppGlyph
36
+ platform: "ios" | "android" | "both"
37
+ }
38
+
39
+ const ios = (id: string, name: string, glyph?: AppGlyph): KnownApp => ({id, name, glyph, platform: "ios"})
40
+ const android = (id: string, name: string, glyph?: AppGlyph): KnownApp => ({id, name, glyph, platform: "android"})
41
+ const both = (id: string, name: string, glyph?: AppGlyph): KnownApp => ({id, name, glyph, platform: "both"})
42
+
43
+ export const KNOWN_APPS: KnownApp[] = [
44
+ // Messaging
45
+ ios("com.apple.MobileSMS", "Messages", "bubble"),
46
+ android("com.google.android.apps.messaging", "Messages", "bubble"),
47
+ android("com.samsung.android.messaging", "Messages", "bubble"),
48
+ ios("net.whatsapp.WhatsApp", "WhatsApp", "bubble"),
49
+ android("com.whatsapp", "WhatsApp", "bubble"),
50
+ ios("ph.telegra.Telegraph", "Telegram", "bubble"),
51
+ android("org.telegram.messenger", "Telegram", "bubble"),
52
+ ios("org.whispersystems.signal", "Signal", "bubble"),
53
+ android("org.thoughtcrime.securesms", "Signal", "bubble"),
54
+ ios("com.facebook.Messenger", "Messenger", "bubble"),
55
+ android("com.facebook.orca", "Messenger", "bubble"),
56
+ ios("com.tinyspeck.chatlyio", "Slack", "bubble"),
57
+ android("com.Slack", "Slack", "bubble"),
58
+ ios("com.hammerandchisel.discord", "Discord", "bubble"),
59
+ android("com.discord", "Discord", "bubble"),
60
+ ios("com.microsoft.skype.teams", "Teams", "bubble"),
61
+ android("com.microsoft.teams", "Teams", "bubble"),
62
+ ios("com.tencent.xin", "WeChat", "bubble"),
63
+ android("com.tencent.mm", "WeChat", "bubble"),
64
+ ios("jp.naver.line", "LINE", "bubble"),
65
+ android("jp.naver.line.android", "LINE", "bubble"),
66
+ ios("com.toyopagroup.picaboo", "Snapchat", "bubble"),
67
+ android("com.snapchat.android", "Snapchat", "bubble"),
68
+ // Social
69
+ ios("com.facebook.Facebook", "Facebook"),
70
+ android("com.facebook.katana", "Facebook"),
71
+ ios("com.atebits.Tweetie2", "X"),
72
+ android("com.twitter.android", "X"),
73
+ ios("com.linkedin.LinkedIn", "LinkedIn"),
74
+ android("com.linkedin.android", "LinkedIn"),
75
+ // Mail
76
+ ios("com.apple.mobilemail", "Mail", "envelope"),
77
+ ios("com.google.Gmail", "Gmail", "envelope"),
78
+ android("com.google.android.gm", "Gmail", "envelope"),
79
+ both("com.microsoft.Office.Outlook", "Outlook", "envelope"),
80
+ ios("com.readdle.smartemail", "Spark", "envelope"),
81
+ android("com.readdle.spark", "Spark", "envelope"),
82
+ ios("ch.protonmail.protonmail", "Proton Mail", "envelope"),
83
+ android("ch.protonmail.android", "Proton Mail", "envelope"),
84
+ // Calendar
85
+ ios("com.apple.mobilecal", "Calendar", "calendar"),
86
+ ios("com.google.calendar", "Google Calendar", "calendar"),
87
+ android("com.google.android.calendar", "Google Calendar", "calendar"),
88
+ android("com.samsung.android.calendar", "Calendar", "calendar"),
89
+ // Phone
90
+ ios("com.apple.mobilephone", "Phone", "phone"),
91
+ ios("com.apple.facetime", "FaceTime", "phone"),
92
+ android("com.google.android.dialer", "Phone", "phone"),
93
+ android("com.samsung.android.dialer", "Phone", "phone"),
94
+ android("com.samsung.android.incallui", "Phone", "phone"),
95
+ // Tasks and reminders
96
+ ios("com.apple.reminders", "Reminders", "check"),
97
+ android("com.google.android.apps.tasks", "Google Tasks", "check"),
98
+ android("com.todoist", "Todoist", "check"),
99
+ ios("com.culturedcode.ThingsiPhone", "Things", "check"),
100
+ // Maps and rides
101
+ ios("com.apple.Maps", "Maps", "pin"),
102
+ ios("com.google.Maps", "Google Maps", "pin"),
103
+ android("com.google.android.apps.maps", "Google Maps", "pin"),
104
+ ios("com.waze.iphone", "Waze", "pin"),
105
+ android("com.waze", "Waze", "pin"),
106
+ ios("com.ubercab.UberClient", "Uber", "car"),
107
+ android("com.ubercab", "Uber", "car"),
108
+ ios("com.zimride.instant", "Lyft", "car"),
109
+ android("me.lyft.android", "Lyft", "car"),
110
+ // Shopping and delivery
111
+ ios("com.amazon.Amazon", "Amazon", "cart"),
112
+ android("com.amazon.mShop.android.shopping", "Amazon", "cart"),
113
+ ios("doordash.DoorDashConsumer", "DoorDash", "cart"),
114
+ android("com.dd.doordash", "DoorDash", "cart"),
115
+ ios("com.ubercab.UberEats", "Uber Eats", "cart"),
116
+ android("com.ubercab.eats", "Uber Eats", "cart"),
117
+ // Music and video
118
+ ios("com.apple.Music", "Music", "note"),
119
+ ios("com.spotify.client", "Spotify", "note"),
120
+ android("com.spotify.music", "Spotify", "note"),
121
+ ios("com.google.ios.youtubemusic", "YouTube Music", "note"),
122
+ android("com.google.android.apps.youtube.music", "YouTube Music", "note"),
123
+ ios("com.google.ios.youtube", "YouTube", "play"),
124
+ android("com.google.android.youtube", "YouTube", "play"),
125
+ ios("com.netflix.Netflix", "Netflix", "play"),
126
+ android("com.netflix.mediaclient", "Netflix", "play"),
127
+ ios("com.zhiliaoapp.musically", "TikTok", "play"),
128
+ android("com.ss.android.ugc.trill", "TikTok", "play"),
129
+ // Photos and camera
130
+ ios("com.apple.mobileslideshow", "Photos", "photo"),
131
+ android("com.google.android.apps.photos", "Google Photos", "photo"),
132
+ ios("com.apple.camera", "Camera", "camera"),
133
+ ios("com.burbn.instagram", "Instagram", "camera"),
134
+ android("com.instagram.android", "Instagram", "camera"),
135
+ // Clock and alarms
136
+ ios("com.apple.mobiletimer", "Clock", "clock"),
137
+ android("com.google.android.deskclock", "Clock", "clock"),
138
+ android("com.sec.android.app.clockpackage", "Clock", "clock"),
139
+ // Health and fitness
140
+ ios("com.apple.Health", "Health", "heart"),
141
+ ios("com.strava.stravaride", "Strava", "heart"),
142
+ android("com.strava", "Strava", "heart"),
143
+ android("com.google.android.apps.fitness", "Google Fit", "heart"),
144
+ ios("com.fitbit.FitbitMobile", "Fitbit", "heart"),
145
+ // Payments
146
+ ios("com.apple.Passbook", "Wallet", "dollar"),
147
+ ios("net.kortina.labs.Venmo", "Venmo", "dollar"),
148
+ ios("com.venmo.TouchFree", "Venmo", "dollar"),
149
+ android("com.venmo", "Venmo", "dollar"),
150
+ ios("com.yourcompany.PPClient", "PayPal", "dollar"),
151
+ android("com.paypal.android.p2pmobile", "PayPal", "dollar"),
152
+ both("com.squareup.cash", "Cash App", "dollar"),
153
+ both("io.heckel.ntfy", "Ntfy", "bubble"),
154
+ ]
155
+
156
+ const BY_ID: ReadonlyMap<string, KnownApp> = new Map(KNOWN_APPS.map((app) => [app.id.toLowerCase(), app]))
157
+
158
+ export function knownApp(packageName: string | undefined): KnownApp | null {
159
+ if (!packageName) return null
160
+ return BY_ID.get(packageName.trim().toLowerCase()) ?? null
161
+ }
162
+
163
+ export function appGlyph(packageName: string | undefined): AppGlyph | null {
164
+ return knownApp(packageName)?.glyph ?? null
165
+ }
166
+
167
+ /** The curated display name for a bare id, when the phone did not report one. */
168
+ export function knownAppName(packageName: string | undefined): string | null {
169
+ return knownApp(packageName)?.name ?? null
170
+ }
@@ -0,0 +1,322 @@
1
+ import {sceneGeometry, geometryEqual, tweenAt, type SceneGeometry} from "./geometry"
2
+ import type {MirrorElement, MirrorSnapshot} from "./types"
3
+
4
+ interface Pose {
5
+ element: MirrorElement
6
+ from: SceneGeometry
7
+ to: SceneGeometry
8
+ start: number
9
+ duration: number
10
+ rotationFrom: number
11
+ rotationTo: number
12
+ rotationStart: number
13
+ rotationDuration: number
14
+ }
15
+
16
+ /** CSS cubic-bezier timing, matching the glasses mirror's easing curves. */
17
+ export function mirrorEase(t: number, name?: string): number {
18
+ if (name === "linear" || t <= 0 || t >= 1) return t
19
+ const [x1, y1, x2, y2] =
20
+ name === "ease-in"
21
+ ? [0.42, 0, 1, 1]
22
+ : name === "ease-out"
23
+ ? [0, 0, 0.58, 1]
24
+ : name === "ease-in-out"
25
+ ? [0.42, 0, 0.58, 1]
26
+ : [0.25, 0.1, 0.25, 1]
27
+ const curve = (u: number, a: number, b: number) => 3 * (1 - u) ** 2 * u * a + 3 * (1 - u) * u ** 2 * b + u ** 3
28
+ let low = 0,
29
+ high = 1
30
+ for (let i = 0; i < 16; i++) {
31
+ const mid = (low + high) / 2
32
+ if (curve(mid, x1, x2) < t) low = mid
33
+ else high = mid
34
+ }
35
+ return curve((low + high) / 2, y1, y2)
36
+ }
37
+
38
+ function poseAt(pose: Pose, now: number): SceneGeometry {
39
+ const geometry = tweenAt(
40
+ pose.from,
41
+ pose.to,
42
+ mirrorEase(pose.duration ? Math.min(1, (now - pose.start) / pose.duration) : 1, pose.element.transition?.easing),
43
+ )
44
+ const rotationT = mirrorEase(
45
+ pose.rotationDuration ? Math.min(1, (now - pose.rotationStart) / pose.rotationDuration) : 1,
46
+ pose.element.transition?.easing,
47
+ )
48
+ return {...geometry, rotation: pose.rotationFrom + (pose.rotationTo - pose.rotationFrom) * rotationT}
49
+ }
50
+
51
+ function canTween(previous: MirrorElement, next: MirrorElement): boolean {
52
+ return (
53
+ next.change !== "created" &&
54
+ previous.type === next.type &&
55
+ previous.text === next.text &&
56
+ previous.data === next.data &&
57
+ previous.d === next.d &&
58
+ previous.fillRule === next.fillRule &&
59
+ previous.style?.fill === next.style?.fill &&
60
+ previous.points?.length === next.points?.length &&
61
+ !(next.type === "image" && (previous.box.w !== next.box.w || previous.box.h !== next.box.h))
62
+ )
63
+ }
64
+
65
+ /** Browser-only renderer. Feed snapshots from session.mirror through your UI channel. */
66
+ export class MirrorCanvasRenderer {
67
+ private frame: MirrorSnapshot | null = null
68
+ private poses = new Map<string, Pose>()
69
+ private images = new Map<string, HTMLCanvasElement | null>()
70
+ private pending = new Set<string>()
71
+ private raf: number | null = null
72
+ private disposed = false
73
+ private readonly ctx: CanvasRenderingContext2D
74
+
75
+ constructor(private readonly canvas: HTMLCanvasElement) {
76
+ const ctx = canvas.getContext("2d")
77
+ if (!ctx) throw new Error("Canvas 2D is unavailable")
78
+ this.ctx = ctx
79
+ }
80
+
81
+ update(frame: MirrorSnapshot): void {
82
+ if (this.disposed || (this.frame && frame.revision < this.frame.revision)) return
83
+ const previous = this.frame
84
+ const scene = frame.content.kind === "scene" ? frame.content : null
85
+ const prior = previous?.content.kind === "scene" ? previous.content : null
86
+ const reset =
87
+ !prior ||
88
+ !scene ||
89
+ previous?.model !== frame.model ||
90
+ previous.appId !== frame.appId ||
91
+ previous.view !== frame.view ||
92
+ prior.epoch !== scene.epoch ||
93
+ frame.revision > previous.revision + 1
94
+ const now = Date.now()
95
+ const poses = new Map<string, Pose>()
96
+ for (const element of scene?.elements ?? []) {
97
+ const to = sceneGeometry(element)
98
+ const prev = reset ? undefined : this.poses.get(element.id)
99
+ const animate = prev && canTween(prev.element, element)
100
+ const duration = element.transition?.durationMs ?? 0
101
+ const current = prev ? poseAt(prev, now) : to
102
+ const sameGeometry = animate && geometryEqual(prev.to, to)
103
+ const sameRotation =
104
+ animate && prev.to.rotation === to.rotation && prev.to.pivotX === to.pivotX && prev.to.pivotY === to.pivotY
105
+ poses.set(element.id, {
106
+ element,
107
+ to,
108
+ from: sameGeometry ? prev.from : animate ? current : to,
109
+ start: sameGeometry ? prev.start : now,
110
+ duration: sameGeometry ? prev.duration : animate ? Math.min(duration, 255 * 33) : 0,
111
+ rotationFrom: sameRotation ? prev.rotationFrom : animate ? current.rotation : to.rotation,
112
+ rotationTo: to.rotation,
113
+ rotationStart: sameRotation ? prev.rotationStart : now,
114
+ rotationDuration: sameRotation ? prev.rotationDuration : animate ? Math.min(duration, 65535) : 0,
115
+ })
116
+ }
117
+ this.poses = poses
118
+ this.frame = frame
119
+ const used = new Set(scene?.elements.flatMap((e) => (e.data ? [e.data] : [])) ?? [])
120
+ if (frame.content.kind === "bitmap") used.add(frame.content.data)
121
+ for (const key of this.images.keys()) if (!used.has(key)) this.images.delete(key)
122
+ this.draw()
123
+ }
124
+
125
+ dispose(): void {
126
+ this.disposed = true
127
+ if (this.raf !== null) cancelAnimationFrame(this.raf)
128
+ this.raf = null
129
+ this.poses.clear()
130
+ this.images.clear()
131
+ this.pending.clear()
132
+ }
133
+
134
+ private image(data: string): HTMLCanvasElement | null {
135
+ if (this.images.has(data)) return this.images.get(data) ?? null
136
+ if (this.pending.has(data)) return null
137
+ this.pending.add(data)
138
+ const image = new Image()
139
+ image.onload = () => {
140
+ this.pending.delete(data)
141
+ if (this.disposed) return
142
+ const content = this.frame?.content
143
+ if (
144
+ !(content?.kind === "bitmap" && content.data === data) &&
145
+ !(content?.kind === "scene" && content.elements.some((e) => e.data === data))
146
+ )
147
+ return
148
+ const surface = document.createElement("canvas")
149
+ surface.width = image.naturalWidth
150
+ surface.height = image.naturalHeight
151
+ const ctx = surface.getContext("2d", {willReadFrequently: true})
152
+ if (!ctx) return
153
+ ctx.drawImage(image, 0, 0)
154
+ const pixels = ctx.getImageData(0, 0, surface.width, surface.height)
155
+ for (let i = 0; i < pixels.data.length; i += 4) {
156
+ const alpha =
157
+ ((0.2126 * pixels.data[i] + 0.7152 * pixels.data[i + 1] + 0.0722 * pixels.data[i + 2]) * pixels.data[i + 3]) /
158
+ 255
159
+ pixels.data[i] = 0
160
+ pixels.data[i + 1] = 255
161
+ pixels.data[i + 2] = 136
162
+ pixels.data[i + 3] = alpha
163
+ }
164
+ ctx.putImageData(pixels, 0, 0)
165
+ this.images.set(data, surface)
166
+ this.draw()
167
+ }
168
+ image.onerror = () => {
169
+ this.pending.delete(data)
170
+ if (!this.disposed) this.images.set(data, null)
171
+ }
172
+ // Encoded images only: never fetch arbitrary URLs from scene data.
173
+ image.src = data.startsWith("data:image/")
174
+ ? data
175
+ : `data:image/${data.startsWith("Qk") ? "bmp" : "png"};base64,${data}`
176
+ return null
177
+ }
178
+
179
+ private draw(): void {
180
+ if (this.disposed || !this.frame) return
181
+ if (this.raf !== null) cancelAnimationFrame(this.raf)
182
+ this.raf = null
183
+ const {canvas, content} = this.frame
184
+ const w = Math.max(1, Math.round(canvas?.width ?? 1)),
185
+ h = Math.max(1, Math.round(canvas?.height ?? 1))
186
+ if (this.canvas.width !== w) this.canvas.width = w
187
+ if (this.canvas.height !== h) this.canvas.height = h
188
+ const ctx = this.ctx
189
+ ctx.clearRect(0, 0, w, h)
190
+ const now = Date.now()
191
+ let animating = false
192
+ if (content.kind === "bitmap") {
193
+ const image = this.image(content.data)
194
+ if (image) {
195
+ const g1 = image.width === 576 && image.height === 135
196
+ const x = g1 ? 29 : 0,
197
+ y = g1 ? 21 : 0
198
+ ctx.drawImage(image, x, y, image.width - x, image.height - y, 0, 0, w, h)
199
+ }
200
+ }
201
+ for (const pose of this.poses.values()) {
202
+ animating ||= now < pose.start + pose.duration || now < pose.rotationStart + pose.rotationDuration
203
+ this.paint(pose.element, poseAt(pose, now))
204
+ }
205
+ if (animating) this.raf = requestAnimationFrame(() => this.draw())
206
+ }
207
+
208
+ private paint(element: MirrorElement, g: SceneGeometry): void {
209
+ const ctx = this.ctx
210
+ ctx.save()
211
+ try {
212
+ if (g.rotation) {
213
+ ctx.translate(g.pivotX, g.pivotY)
214
+ ctx.rotate((g.rotation * Math.PI) / 180)
215
+ ctx.translate(-g.pivotX, -g.pivotY)
216
+ }
217
+ if (element.type === "image") {
218
+ const image = element.data ? this.image(element.data) : null
219
+ ctx.globalAlpha = g.level
220
+ if (image) ctx.drawImage(image, g.x, g.y, g.w, g.h)
221
+ return
222
+ }
223
+ if (element.type === "text") {
224
+ if (g.stroke > 0) {
225
+ const border = new Path2D()
226
+ border.roundRect(g.x, g.y, g.w, g.h, Math.max(0, Math.min(g.radius, g.w / 2, g.h / 2)))
227
+ this.path(border, false, g)
228
+ }
229
+ ctx.beginPath()
230
+ ctx.rect(g.x, g.y, g.w, g.h)
231
+ ctx.clip()
232
+ const mono = element.style?.font === "mono",
233
+ size = mono ? element.style?.fontSize || 20 : 20
234
+ const pitch = mono ? Math.round(size * 1.2) : 27
235
+ ctx.font = `${size}px ${mono ? "monospace" : "Arial, sans-serif"}`
236
+ const metrics = ctx.measureText("0"),
237
+ cell = Math.max(1, Math.round(metrics.width))
238
+ const ascent = metrics.fontBoundingBoxAscent || size * 0.8,
239
+ descent = metrics.fontBoundingBoxDescent || size * 0.2
240
+ const baseline = mono ? Math.round((pitch - ascent - descent) / 2 + ascent) : 22
241
+ ctx.fillStyle = `rgba(0,255,136,${g.level})`
242
+ ;(element.text ?? "").split("\n").forEach((line, row) => {
243
+ if (row > 0 && (mono ? 2 + row * pitch >= g.h - 2 : (row + 1) * pitch > g.h)) return
244
+ let x = g.x + 2
245
+ for (const char of line) {
246
+ ctx.fillText(char, x, g.y + 2 + row * pitch + baseline)
247
+ x += mono ? cell : this.frame?.glyphWidths[char] ?? ctx.measureText(char).width
248
+ }
249
+ })
250
+ return
251
+ }
252
+ let path = new Path2D()
253
+ const pts = g.points,
254
+ cx = g.x + g.w / 2,
255
+ cy = g.y + g.h / 2,
256
+ r = Math.min(g.w, g.h) / 2
257
+ switch (element.type) {
258
+ case "rect":
259
+ path.roundRect(g.x, g.y, g.w, g.h, Math.max(0, Math.min(g.radius, g.w / 2, g.h / 2)))
260
+ break
261
+ case "circle":
262
+ path.arc(cx, cy, r, 0, Math.PI * 2)
263
+ break
264
+ case "arc":
265
+ case "pie": {
266
+ const sweep = g.endDeg - g.startDeg
267
+ if (!sweep) return
268
+ if (Math.abs(sweep) >= 360) path.arc(cx, cy, r, 0, Math.PI * 2)
269
+ else {
270
+ if (element.type === "pie") path.moveTo(cx, cy)
271
+ path.arc(cx, cy, r, (g.startDeg * Math.PI) / 180, (g.endDeg * Math.PI) / 180, sweep < 0)
272
+ if (element.type === "pie") path.closePath()
273
+ }
274
+ break
275
+ }
276
+ case "line":
277
+ case "triangle":
278
+ case "quad":
279
+ if (pts.length < 4) return
280
+ path.moveTo(pts[0], pts[1])
281
+ for (let i = 2; i < pts.length; i += 2) path.lineTo(pts[i], pts[i + 1])
282
+ if (element.type !== "line") path.closePath()
283
+ break
284
+ case "bezier":
285
+ path.moveTo(pts[0], pts[1])
286
+ if (pts.length === 6) path.quadraticCurveTo(pts[2], pts[3], pts[4], pts[5])
287
+ else if (pts.length === 8) path.bezierCurveTo(pts[2], pts[3], pts[4], pts[5], pts[6], pts[7])
288
+ else return
289
+ break
290
+ case "svg":
291
+ if (!element.d) return
292
+ path = new Path2D(element.d)
293
+ ctx.translate(g.x, g.y)
294
+ ctx.scale(g.pathScale, g.pathScale)
295
+ break
296
+ default:
297
+ return
298
+ }
299
+ const filled =
300
+ element.type === "svg" ||
301
+ element.type === "pie" ||
302
+ (["rect", "circle", "triangle", "quad"].includes(element.type) && element.style?.fill === true)
303
+ this.path(path, filled, g, element.fillRule === "evenodd" ? "evenodd" : "nonzero")
304
+ } finally {
305
+ ctx.restore()
306
+ }
307
+ }
308
+
309
+ private path(path: Path2D, fill: boolean, g: SceneGeometry, rule: CanvasFillRule = "nonzero"): void {
310
+ const ctx = this.ctx
311
+ ctx.lineWidth = Math.max(1, g.stroke)
312
+ // Replace the shape's covered pixels, including level-zero erasure.
313
+ ctx.globalCompositeOperation = "destination-out"
314
+ ctx.fillStyle = ctx.strokeStyle = "black"
315
+ if (fill) ctx.fill(path, rule)
316
+ else ctx.stroke(path)
317
+ ctx.globalCompositeOperation = "source-over"
318
+ ctx.fillStyle = ctx.strokeStyle = `rgba(0,255,136,${g.level})`
319
+ if (fill) ctx.fill(path, rule)
320
+ else ctx.stroke(path)
321
+ }
322
+ }