@glassly/engine 0.4.0-dev.1067 → 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.
- package/build/engine.d.ts +1 -0
- package/build/engine.d.ts.map +1 -1
- package/build/facades/permissions.d.ts.map +1 -1
- package/build/facades/permissions.js +0 -2
- package/build/facades/permissions.js.map +1 -1
- package/build/facades/phoneNotifications.d.ts +7 -0
- package/build/facades/phoneNotifications.d.ts.map +1 -1
- package/build/facades/phoneNotifications.js +6 -0
- package/build/facades/phoneNotifications.js.map +1 -1
- package/build/generated/releaseMetadata.js +3 -3
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/index.d.ts +1 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js.map +1 -1
- package/build/services/LocalMiniappRuntime.d.ts +3 -0
- package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
- package/build/services/LocalMiniappRuntime.js +67 -1
- package/build/services/LocalMiniappRuntime.js.map +1 -1
- package/build/services/notifications/miniappNotifications.d.ts +31 -0
- package/build/services/notifications/miniappNotifications.d.ts.map +1 -0
- package/build/services/notifications/miniappNotifications.js +24 -0
- package/build/services/notifications/miniappNotifications.js.map +1 -0
- package/package.json +7 -7
- package/src/facades/permissions.ts +0 -2
- package/src/facades/phoneNotifications.ts +10 -0
- package/src/generated/releaseMetadata.ts +3 -3
- package/src/index.ts +1 -0
- package/src/services/LocalMiniappRuntime.ts +79 -1
- package/src/services/notifications/miniappNotifications.ts +53 -0
|
@@ -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.
|
|
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.
|
|
101
|
-
"@glassly/cloud-client": "0.4.0-dev.
|
|
102
|
-
"@glassly/cloud-protocol": "0.4.0-dev.
|
|
103
|
-
"@glassly/crust": "0.4.0-dev.
|
|
104
|
-
"@glassly/miniapp": "0.4.0-dev.
|
|
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.
|
|
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.
|
|
16
|
-
"releaseSetId": "glassly-0.4.0-dev.
|
|
17
|
-
"sourceCommit": "
|
|
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
|
-
|
|
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
|
+
}
|