@glassly/engine 0.4.0-dev.1066 → 0.4.0-dev.1068

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.
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Notifications miniapps post (`session.phone.notifications.post`) on their
3
+ * way into the host's phone-notification path. The runtime validates and
4
+ * emits here; the host subscribes through `engine.phoneNotifications` and
5
+ * feeds them to the same fan-out as the phone's own, with `system: false`
6
+ * and `source` set, so the dashboard, other readers and presentation treat
7
+ * them alike.
8
+ */
9
+ export interface MiniappNotificationPosted {
10
+ type: "posted";
11
+ notificationId: string;
12
+ source: string;
13
+ app: string;
14
+ title: string;
15
+ content: string;
16
+ timestamp: number;
17
+ }
18
+ export interface MiniappNotificationDismissed {
19
+ type: "dismissed";
20
+ notificationId: string;
21
+ source: string;
22
+ timestamp: number;
23
+ }
24
+ export type MiniappNotificationEvent = MiniappNotificationPosted | MiniappNotificationDismissed;
25
+ export declare const MINIAPP_NOTIFICATION_TITLE_MAX = 200;
26
+ export declare const MINIAPP_NOTIFICATION_CONTENT_MAX = 2000;
27
+ export declare const MINIAPP_NOTIFICATION_ID_MAX = 128;
28
+ export declare function miniappNotificationId(source: string, id: string): string;
29
+ export declare function emitMiniappNotification(event: MiniappNotificationEvent): void;
30
+ export declare function onMiniappNotification(listener: (event: MiniappNotificationEvent) => void): () => void;
31
+ //# sourceMappingURL=miniappNotifications.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"miniappNotifications.d.ts","sourceRoot":"","sources":["../../../src/services/notifications/miniappNotifications.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,QAAQ,CAAA;IACd,cAAc,EAAE,MAAM,CAAA;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,4BAA4B;IAC3C,IAAI,EAAE,WAAW,CAAA;IACjB,cAAc,EAAE,MAAM,CAAA;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,MAAM,wBAAwB,GAAG,yBAAyB,GAAG,4BAA4B,CAAA;AAE/F,eAAO,MAAM,8BAA8B,MAAM,CAAA;AACjD,eAAO,MAAM,gCAAgC,OAAQ,CAAA;AACrD,eAAO,MAAM,2BAA2B,MAAM,CAAA;AAI9C,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAExE;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,wBAAwB,GAAG,IAAI,CAQ7E;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,wBAAwB,KAAK,IAAI,GAAG,MAAM,IAAI,CAKrG"}
@@ -0,0 +1,24 @@
1
+ export const MINIAPP_NOTIFICATION_TITLE_MAX = 200;
2
+ export const MINIAPP_NOTIFICATION_CONTENT_MAX = 2_000;
3
+ export const MINIAPP_NOTIFICATION_ID_MAX = 128;
4
+ const listeners = new Set();
5
+ export function miniappNotificationId(source, id) {
6
+ return `${source}:${id}`;
7
+ }
8
+ export function emitMiniappNotification(event) {
9
+ for (const listener of listeners) {
10
+ try {
11
+ listener(event);
12
+ }
13
+ catch (err) {
14
+ console.warn(`miniappNotifications: listener threw: ${err?.message ?? err}`);
15
+ }
16
+ }
17
+ }
18
+ export function onMiniappNotification(listener) {
19
+ listeners.add(listener);
20
+ return () => {
21
+ listeners.delete(listener);
22
+ };
23
+ }
24
+ //# sourceMappingURL=miniappNotifications.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"miniappNotifications.js","sourceRoot":"","sources":["../../../src/services/notifications/miniappNotifications.ts"],"names":[],"mappings":"AA2BA,MAAM,CAAC,MAAM,8BAA8B,GAAG,GAAG,CAAA;AACjD,MAAM,CAAC,MAAM,gCAAgC,GAAG,KAAK,CAAA;AACrD,MAAM,CAAC,MAAM,2BAA2B,GAAG,GAAG,CAAA;AAE9C,MAAM,SAAS,GAAG,IAAI,GAAG,EAA6C,CAAA;AAEtE,MAAM,UAAU,qBAAqB,CAAC,MAAc,EAAE,EAAU;IAC9D,OAAO,GAAG,MAAM,IAAI,EAAE,EAAE,CAAA;AAC1B,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,KAA+B;IACrE,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,IAAI,CAAC;YACH,QAAQ,CAAC,KAAK,CAAC,CAAA;QACjB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,yCAA0C,GAAa,EAAE,OAAO,IAAI,GAAG,EAAE,CAAC,CAAA;QACzF,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,QAAmD;IACvF,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;IACvB,OAAO,GAAG,EAAE;QACV,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;IAC5B,CAAC,CAAA;AACH,CAAC","sourcesContent":["/**\n * Notifications miniapps post (`session.phone.notifications.post`) on their\n * way into the host's phone-notification path. The runtime validates and\n * emits here; the host subscribes through `engine.phoneNotifications` and\n * feeds them to the same fan-out as the phone's own, with `system: false`\n * and `source` set, so the dashboard, other readers and presentation treat\n * them alike.\n */\nexport interface MiniappNotificationPosted {\n type: \"posted\"\n notificationId: string\n source: string\n app: string\n title: string\n content: string\n timestamp: number\n}\n\nexport interface MiniappNotificationDismissed {\n type: \"dismissed\"\n notificationId: string\n source: string\n timestamp: number\n}\n\nexport type MiniappNotificationEvent = MiniappNotificationPosted | MiniappNotificationDismissed\n\nexport const MINIAPP_NOTIFICATION_TITLE_MAX = 200\nexport const MINIAPP_NOTIFICATION_CONTENT_MAX = 2_000\nexport const MINIAPP_NOTIFICATION_ID_MAX = 128\n\nconst listeners = new Set<(event: MiniappNotificationEvent) => void>()\n\nexport function miniappNotificationId(source: string, id: string): string {\n return `${source}:${id}`\n}\n\nexport function emitMiniappNotification(event: MiniappNotificationEvent): void {\n for (const listener of listeners) {\n try {\n listener(event)\n } catch (err) {\n console.warn(`miniappNotifications: listener threw: ${(err as Error)?.message ?? err}`)\n }\n }\n}\n\nexport function onMiniappNotification(listener: (event: MiniappNotificationEvent) => void): () => void {\n listeners.add(listener)\n return () => {\n listeners.delete(listener)\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glassly/engine",
3
- "version": "0.4.0-dev.1066",
3
+ "version": "0.4.0-dev.1068",
4
4
  "description": "Glassly Engine — miniapp registry, WebView bridge, and miniapp store",
5
5
  "main": "build/index.js",
6
6
  "react-native": "src/index.ts",
@@ -97,17 +97,17 @@
97
97
  "registry": "https://registry.npmjs.org/"
98
98
  },
99
99
  "dependencies": {
100
- "@glassly/bluetooth-sdk": "0.4.0-dev.1066",
101
- "@glassly/cloud-client": "0.4.0-dev.1066",
102
- "@glassly/cloud-protocol": "0.4.0-dev.1066",
103
- "@glassly/crust": "0.4.0-dev.1066",
104
- "@glassly/miniapp": "0.4.0-dev.1066",
100
+ "@glassly/bluetooth-sdk": "0.4.0-dev.1068",
101
+ "@glassly/cloud-client": "0.4.0-dev.1068",
102
+ "@glassly/cloud-protocol": "0.4.0-dev.1068",
103
+ "@glassly/crust": "0.4.0-dev.1068",
104
+ "@glassly/miniapp": "0.4.0-dev.1068",
105
105
  "buffer": "^6.0.3",
106
106
  "events": "^3.3.0",
107
107
  "react-native-marked": "8.3.4",
108
108
  "semver": "^7.8.6",
109
109
  "typesafe-ts": "^1.7.1",
110
- "@glassly/llm-providers": "0.4.0-dev.1066",
110
+ "@glassly/llm-providers": "0.4.0-dev.1068",
111
111
  "@huggingface/jinja": "^0.5.12",
112
112
  "jsonrepair": "^3.15.0"
113
113
  },
@@ -125,14 +125,12 @@ export const permissions = {
125
125
  features.add(PermissionFeatures.CALENDAR)
126
126
  features.add(PermissionFeatures.LOCATION)
127
127
  features.add(PermissionFeatures.BACKGROUND_LOCATION)
128
- features.add(PermissionFeatures.POST_NOTIFICATIONS)
129
128
  features.add("read_notifications")
130
129
  break
131
130
  case "MICROPHONE":
132
131
  case "CALENDAR":
133
132
  case "LOCATION":
134
133
  case "BACKGROUND_LOCATION":
135
- case "POST_NOTIFICATIONS":
136
134
  features.add(PermissionFeatures[permission.type])
137
135
  break
138
136
  case "READ_NOTIFICATIONS":
@@ -23,6 +23,9 @@ import {
23
23
  nativeNotificationsSupported,
24
24
  uploadPhoneNotification,
25
25
  } from "../services/PhoneNotificationsSync"
26
+ import {onMiniappNotification, type MiniappNotificationEvent} from "../services/notifications/miniappNotifications"
27
+
28
+ export type {MiniappNotificationEvent} from "../services/notifications/miniappNotifications"
26
29
 
27
30
  export const phoneNotifications = {
28
31
  /**
@@ -65,6 +68,13 @@ export const phoneNotifications = {
65
68
  * surface runs and draw only when the answer is `host`.
66
69
  */
67
70
  presentationOwner: currentPresentationOwner,
71
+ /**
72
+ * Notifications miniapps post and dismiss. Hosts feed them to the same
73
+ * fan-out as the phone's, marked `system: false` with `source` set.
74
+ */
75
+ onMiniappNotification: (listener: (event: MiniappNotificationEvent) => void): (() => void) =>
76
+ onMiniappNotification(listener),
77
+
68
78
  /** Hand a captured notification to firmware that presents uploaded content (owner `firmware`, Android). */
69
79
  uploadToGlasses: (event: PhoneNotificationEvent): Promise<void> => uploadPhoneNotification(event),
70
80
 
@@ -12,9 +12,9 @@ export interface EngineReleaseMetadata {
12
12
  export const ENGINE_RELEASE_METADATA: Readonly<EngineReleaseMetadata> = Object.freeze({
13
13
  "schemaVersion": 1,
14
14
  "familyBaseVersion": "0.4.0",
15
- "releaseIdentity": "0.4.0-dev.1066",
16
- "releaseSetId": "glassly-0.4.0-dev.1066",
17
- "sourceCommit": "1df55b8e89eeec2cb01fa011748fed92c1f5e920",
15
+ "releaseIdentity": "0.4.0-dev.1068",
16
+ "releaseSetId": "glassly-0.4.0-dev.1068",
17
+ "sourceCommit": "633efe3678d98aeda2d2f47346a8f35e80ab3992",
18
18
  "otaManifestUrl": null,
19
19
  "otaManifestSha256": null
20
20
  })
package/src/index.ts CHANGED
@@ -23,6 +23,7 @@ export type {
23
23
  } from "./facades/reports"
24
24
  export type {IslandNotification, IslandNotificationKind} from "./facades/notifications"
25
25
  export type {PresentationOwner} from "./services/notifications/notificationPolicy"
26
+ export type {MiniappNotificationEvent} from "./services/notifications/miniappNotifications"
26
27
  export {DEFAULT_HIDDEN_PACKAGES, isGatedByDevTools, isSystemMiniapp} from "./stores/appVisibility"
27
28
  export type {WifiSearchResult} from "./facades/glassesWifi"
28
29
  export type {IdentitySnapshot} from "./services/PairingIdentity"
@@ -83,6 +83,13 @@ import overlayPages from "./pages/OverlayPages"
83
83
  import dashboardOpenTrace from "./debug/DashboardOpenTrace"
84
84
  import {isAppForegrounded, isPhoneActive, onAppForegroundChange, onPhoneActivityChange} from "./PhoneActivity"
85
85
  import {installedNotificationApps} from "./PhoneNotificationsSync"
86
+ import {
87
+ emitMiniappNotification,
88
+ MINIAPP_NOTIFICATION_CONTENT_MAX,
89
+ MINIAPP_NOTIFICATION_ID_MAX,
90
+ MINIAPP_NOTIFICATION_TITLE_MAX,
91
+ miniappNotificationId,
92
+ } from "./notifications/miniappNotifications"
86
93
  import {MiniappIcons} from "./MiniappIcons"
87
94
  import {pairedControllersFrom, readyControllersKey} from "./controllers"
88
95
  import {
@@ -1513,6 +1520,12 @@ class LocalMiniappRuntime {
1513
1520
  case MiniappRequestType.NOTIFICATION_APPS_LIST:
1514
1521
  void this.handleNotificationAppsList(packageName, requestId)
1515
1522
  break
1523
+ case MiniappRequestType.NOTIFICATION_POST:
1524
+ this.handleNotificationPost(packageName, payload, requestId)
1525
+ break
1526
+ case MiniappRequestType.NOTIFICATION_DISMISS:
1527
+ this.handleNotificationDismiss(packageName, payload, requestId)
1528
+ break
1516
1529
  case MiniappRequestType.NATIVE_NAV:
1517
1530
  this.handleNativeNav(packageName, payload, requestId)
1518
1531
  break
@@ -2792,7 +2805,17 @@ class LocalMiniappRuntime {
2792
2805
  return
2793
2806
  }
2794
2807
  try {
2795
- this.sendResult(packageName, requestId, true, await installedNotificationApps())
2808
+ const posters = this.interopApps().filter(
2809
+ (app) => app.packageName && app.permissions?.some((permission) => permission.type === "POST_NOTIFICATIONS"),
2810
+ )
2811
+ const miniapps = await Promise.all(
2812
+ posters.map(async (app) => ({
2813
+ packageName: app.packageName,
2814
+ appName: app.name || app.packageName,
2815
+ icon: (await this.miniappIcons.read(app)) ?? null,
2816
+ })),
2817
+ )
2818
+ this.sendResult(packageName, requestId, true, [...(await installedNotificationApps()), ...miniapps])
2796
2819
  } catch (err) {
2797
2820
  this.sendResult(packageName, requestId, false, undefined, {
2798
2821
  code: MiniappErrorCode.INTERNAL,
@@ -2801,6 +2824,60 @@ class LocalMiniappRuntime {
2801
2824
  }
2802
2825
  }
2803
2826
 
2827
+ private handleNotificationPost(packageName: string, payload: Record<string, unknown>, requestId?: string): void {
2828
+ if (!this.requirePermission(packageName, requestId, "POST_NOTIFICATIONS", "to post notifications")) return
2829
+ const title = typeof payload.title === "string" ? payload.title.slice(0, MINIAPP_NOTIFICATION_TITLE_MAX) : ""
2830
+ const content =
2831
+ typeof payload.content === "string" ? payload.content.slice(0, MINIAPP_NOTIFICATION_CONTENT_MAX) : ""
2832
+ if (!title.trim() && !content.trim()) {
2833
+ this.sendResult(packageName, requestId, false, undefined, {
2834
+ code: MiniappErrorCode.INVALID_ARGUMENT,
2835
+ message: "A notification needs a title or content",
2836
+ })
2837
+ return
2838
+ }
2839
+ if (payload.id !== undefined && (typeof payload.id !== "string" || !payload.id.trim())) {
2840
+ this.sendResult(packageName, requestId, false, undefined, {
2841
+ code: MiniappErrorCode.INVALID_ARGUMENT,
2842
+ message: "A notification id must be a non-empty string",
2843
+ })
2844
+ return
2845
+ }
2846
+ const id =
2847
+ typeof payload.id === "string"
2848
+ ? payload.id.trim().slice(0, MINIAPP_NOTIFICATION_ID_MAX)
2849
+ : `${Date.now().toString(36)}-${++this.notificationSeq}`
2850
+ const app = this.interopApps().find((candidate) => candidate.packageName === packageName)
2851
+ emitMiniappNotification({
2852
+ type: "posted",
2853
+ notificationId: miniappNotificationId(packageName, id),
2854
+ source: packageName,
2855
+ app: app?.name || packageName,
2856
+ title,
2857
+ content,
2858
+ timestamp: Date.now(),
2859
+ })
2860
+ this.sendResult(packageName, requestId, true, {id})
2861
+ }
2862
+
2863
+ private handleNotificationDismiss(packageName: string, payload: Record<string, unknown>, requestId?: string): void {
2864
+ if (!this.requirePermission(packageName, requestId, "POST_NOTIFICATIONS", "to dismiss notifications")) return
2865
+ if (typeof payload.id !== "string" || !payload.id.trim()) {
2866
+ this.sendResult(packageName, requestId, false, undefined, {
2867
+ code: MiniappErrorCode.INVALID_ARGUMENT,
2868
+ message: "dismiss needs the id post() returned",
2869
+ })
2870
+ return
2871
+ }
2872
+ emitMiniappNotification({
2873
+ type: "dismissed",
2874
+ notificationId: miniappNotificationId(packageName, payload.id.trim().slice(0, MINIAPP_NOTIFICATION_ID_MAX)),
2875
+ source: packageName,
2876
+ timestamp: Date.now(),
2877
+ })
2878
+ this.sendResult(packageName, requestId, true)
2879
+ }
2880
+
2804
2881
  /**
2805
2882
  * Push bridged settings as they change: stored keys from the store, the
2806
2883
  * derived keys whenever their inputs move (phone activity, the display,
@@ -6269,6 +6346,7 @@ class LocalMiniappRuntime {
6269
6346
  }
6270
6347
 
6271
6348
  private miniappIcons = new MiniappIcons()
6349
+ private notificationSeq = 0
6272
6350
 
6273
6351
  private async handleMiniappsList(
6274
6352
  packageName: string,
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Notifications miniapps post (`session.phone.notifications.post`) on their
3
+ * way into the host's phone-notification path. The runtime validates and
4
+ * emits here; the host subscribes through `engine.phoneNotifications` and
5
+ * feeds them to the same fan-out as the phone's own, with `system: false`
6
+ * and `source` set, so the dashboard, other readers and presentation treat
7
+ * them alike.
8
+ */
9
+ export interface MiniappNotificationPosted {
10
+ type: "posted"
11
+ notificationId: string
12
+ source: string
13
+ app: string
14
+ title: string
15
+ content: string
16
+ timestamp: number
17
+ }
18
+
19
+ export interface MiniappNotificationDismissed {
20
+ type: "dismissed"
21
+ notificationId: string
22
+ source: string
23
+ timestamp: number
24
+ }
25
+
26
+ export type MiniappNotificationEvent = MiniappNotificationPosted | MiniappNotificationDismissed
27
+
28
+ export const MINIAPP_NOTIFICATION_TITLE_MAX = 200
29
+ export const MINIAPP_NOTIFICATION_CONTENT_MAX = 2_000
30
+ export const MINIAPP_NOTIFICATION_ID_MAX = 128
31
+
32
+ const listeners = new Set<(event: MiniappNotificationEvent) => void>()
33
+
34
+ export function miniappNotificationId(source: string, id: string): string {
35
+ return `${source}:${id}`
36
+ }
37
+
38
+ export function emitMiniappNotification(event: MiniappNotificationEvent): void {
39
+ for (const listener of listeners) {
40
+ try {
41
+ listener(event)
42
+ } catch (err) {
43
+ console.warn(`miniappNotifications: listener threw: ${(err as Error)?.message ?? err}`)
44
+ }
45
+ }
46
+ }
47
+
48
+ export function onMiniappNotification(listener: (event: MiniappNotificationEvent) => void): () => void {
49
+ listeners.add(listener)
50
+ return () => {
51
+ listeners.delete(listener)
52
+ }
53
+ }