@glassly/engine 0.4.0-beta.1052 → 0.4.0-beta.1076
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/JSRouter.js +2 -2
- package/build/services/JSRouter.js.map +1 -1
- package/build/services/LocalLlmModelManager.d.ts.map +1 -1
- package/build/services/LocalLlmModelManager.js +4 -3
- package/build/services/LocalLlmModelManager.js.map +1 -1
- package/build/services/LocalLlmProvider.d.ts.map +1 -1
- package/build/services/LocalLlmProvider.js +5 -6
- package/build/services/LocalLlmProvider.js.map +1 -1
- package/build/services/LocalMiniappRuntime.d.ts +4 -0
- package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
- package/build/services/LocalMiniappRuntime.js +73 -4
- 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/build/services/pages/pageEligibility.d.ts +1 -2
- package/build/services/pages/pageEligibility.d.ts.map +1 -1
- package/build/services/pages/pageEligibility.js +4 -2
- package/build/services/pages/pageEligibility.js.map +1 -1
- package/build/services/uiShim.d.ts.map +1 -1
- package/build/services/uiShim.js +14 -0
- package/build/services/uiShim.js.map +1 -1
- package/build/stores/settings.d.ts.map +1 -1
- package/build/stores/settings.js +7 -8
- package/build/stores/settings.js.map +1 -1
- package/build/types/capabilities/nimo.d.ts.map +1 -1
- package/build/types/capabilities/nimo.js +2 -0
- package/build/types/capabilities/nimo.js.map +1 -1
- package/package.json +15 -15
- 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/JSRouter.ts +2 -2
- package/src/services/LocalLlmModelManager.ts +4 -3
- package/src/services/LocalLlmProvider.ts +6 -7
- package/src/services/LocalMiniappRuntime.ts +86 -4
- package/src/services/notifications/miniappNotifications.ts +53 -0
- package/src/services/pages/pageEligibility.ts +3 -2
- package/src/services/uiShim.ts +14 -0
- package/src/stores/settings.ts +7 -8
- package/src/types/capabilities/nimo.ts +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glassly/engine",
|
|
3
|
-
"version": "0.4.0-beta.
|
|
3
|
+
"version": "0.4.0-beta.1076",
|
|
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,26 +97,26 @@
|
|
|
97
97
|
"registry": "https://registry.npmjs.org/"
|
|
98
98
|
},
|
|
99
99
|
"dependencies": {
|
|
100
|
-
"@glassly/bluetooth-sdk": "0.4.0-beta.
|
|
101
|
-
"@glassly/cloud-client": "0.4.0-beta.
|
|
102
|
-
"@glassly/cloud-protocol": "0.4.0-beta.
|
|
103
|
-
"@glassly/crust": "0.4.0-beta.
|
|
104
|
-
"@glassly/miniapp": "0.4.0-beta.
|
|
100
|
+
"@glassly/bluetooth-sdk": "0.4.0-beta.1076",
|
|
101
|
+
"@glassly/cloud-client": "0.4.0-beta.1076",
|
|
102
|
+
"@glassly/cloud-protocol": "0.4.0-beta.1076",
|
|
103
|
+
"@glassly/crust": "0.4.0-beta.1076",
|
|
104
|
+
"@glassly/miniapp": "0.4.0-beta.1076",
|
|
105
105
|
"buffer": "^6.0.3",
|
|
106
106
|
"events": "^3.3.0",
|
|
107
|
-
"react-native-marked": "8.3.
|
|
108
|
-
"semver": "^7.
|
|
107
|
+
"react-native-marked": "8.3.4",
|
|
108
|
+
"semver": "^7.8.6",
|
|
109
109
|
"typesafe-ts": "^1.7.1",
|
|
110
|
-
"@glassly/llm-providers": "0.4.0-beta.
|
|
111
|
-
"@huggingface/jinja": "^0.5.
|
|
112
|
-
"jsonrepair": "^3.
|
|
110
|
+
"@glassly/llm-providers": "0.4.0-beta.1076",
|
|
111
|
+
"@huggingface/jinja": "^0.5.12",
|
|
112
|
+
"jsonrepair": "^3.15.0"
|
|
113
113
|
},
|
|
114
114
|
"devDependencies": {
|
|
115
115
|
"@glassly/llm-providers": "workspace:*",
|
|
116
|
-
"@types/semver": "^7.
|
|
116
|
+
"@types/semver": "^7.8.0",
|
|
117
117
|
"expo-module-scripts": "^56.0.3",
|
|
118
|
-
"expo-modules-core": "^58.0.
|
|
119
|
-
"react-native-nitro-modules": "^0.37.
|
|
118
|
+
"expo-modules-core": "^58.0.30",
|
|
119
|
+
"react-native-nitro-modules": "^0.37.2",
|
|
120
120
|
"typescript": "~6.0.3"
|
|
121
121
|
},
|
|
122
122
|
"peerDependencies": {
|
|
@@ -155,7 +155,7 @@
|
|
|
155
155
|
"@dr.pogodin/react-native-fs": "*",
|
|
156
156
|
"@craftzdog/react-native-buffer": "*",
|
|
157
157
|
"axios": "*",
|
|
158
|
-
"react-native-executorch": ">=0.
|
|
158
|
+
"react-native-executorch": ">=0.11.0 <0.12",
|
|
159
159
|
"react-native-blob-util": "*"
|
|
160
160
|
}
|
|
161
161
|
}
|
|
@@ -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-beta.
|
|
16
|
-
"releaseSetId": "glassly-0.4.0-beta.
|
|
17
|
-
"sourceCommit": "
|
|
15
|
+
"releaseIdentity": "0.4.0-beta.1076",
|
|
16
|
+
"releaseSetId": "glassly-0.4.0-beta.1076",
|
|
17
|
+
"sourceCommit": "3021c69f679562e3de8a57d76c806d1c8173c22a",
|
|
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"
|
package/src/services/JSRouter.ts
CHANGED
|
@@ -381,6 +381,8 @@ export class JSRouter {
|
|
|
381
381
|
BgTimer.clearTimeout(pending)
|
|
382
382
|
this.respawnTimers.delete(packageName)
|
|
383
383
|
}
|
|
384
|
+
miniappRunningRegistry.remove(packageName)
|
|
385
|
+
this.runtime.releaseWidgetHost(packageName)
|
|
384
386
|
await this.runtime.notifyWillDisconnect(packageName, "stopped")
|
|
385
387
|
this.spawnCache.delete(packageName)
|
|
386
388
|
this.crashController?.onKill(packageName)
|
|
@@ -388,8 +390,6 @@ export class JSRouter {
|
|
|
388
390
|
// drop out of native nav mode (crash-respawns don't take this path).
|
|
389
391
|
this.runtime.unregisterApp(packageName, {endNavigation: true})
|
|
390
392
|
this.registered.delete(packageName)
|
|
391
|
-
// JSContext torn down → miniapp is no longer "running".
|
|
392
|
-
miniappRunningRegistry.remove(packageName)
|
|
393
393
|
if (this.crust.jsKill) {
|
|
394
394
|
try {
|
|
395
395
|
await this.crust.jsKill(packageName)
|
|
@@ -56,9 +56,10 @@ export interface LocalLlmDownloadStatus {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
// XNNPACK 8da4w exports from software-mansion/react-native-executorch-qwen-3,
|
|
59
|
-
// pinned to the v0.10.0 release tag
|
|
60
|
-
//
|
|
61
|
-
//
|
|
59
|
+
// pinned to the v0.10.0 release tag. They export every method the 0.11 text
|
|
60
|
+
// runner validates; moving to the v0.11.0 re-exports changes the file sizes and
|
|
61
|
+
// makes every existing download re-fetch. modelBytes are the .pte sizes on the
|
|
62
|
+
// Hub; sizeBytes adds the tokenizer files (~11 MB).
|
|
62
63
|
const QWEN3_BASE = "https://huggingface.co/software-mansion/react-native-executorch-qwen-3/resolve/v0.10.0"
|
|
63
64
|
const TOKENIZER_BYTES = 11_431_080
|
|
64
65
|
const MB = 1024 * 1024
|
|
@@ -33,7 +33,7 @@ type Rne = typeof import("react-native-executorch")
|
|
|
33
33
|
// The runner API lives on the `llm` namespace; derive its types from there so
|
|
34
34
|
// the import stays a type-only reference (no native module at load time).
|
|
35
35
|
type Tokenizer = ReturnType<Rne["nlp"]["loadTokenizer"]>
|
|
36
|
-
type LLMRunner = ReturnType<Rne["llm"]["createLLMRunner"]
|
|
36
|
+
type LLMRunner = Awaited<ReturnType<Rne["llm"]["createLLMRunner"]>>
|
|
37
37
|
type LLMGenerationStats = ReturnType<LLMRunner["generate"]>
|
|
38
38
|
type LLMGenerationConfig = NonNullable<Parameters<LLMRunner["generate"]>[1]>
|
|
39
39
|
|
|
@@ -238,15 +238,15 @@ class LocalLlmProvider {
|
|
|
238
238
|
|
|
239
239
|
const elapsedMs = Date.now() - startedAt
|
|
240
240
|
console.log(
|
|
241
|
-
`${LOG_TAG}: ${model.modelId} prompt=${generated.stats.
|
|
241
|
+
`${LOG_TAG}: ${model.modelId} prompt=${generated.stats.prefill.numTokens} out=${generated.stats.numTokens} tokens in ${elapsedMs} ms`,
|
|
242
242
|
)
|
|
243
243
|
|
|
244
244
|
const seed = `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`
|
|
245
245
|
const completion = finalizeLocalResult(
|
|
246
246
|
{
|
|
247
247
|
text: generated.text,
|
|
248
|
-
promptTokens: generated.stats.
|
|
249
|
-
generatedTokens: generated.stats.
|
|
248
|
+
promptTokens: generated.stats.prefill.numTokens,
|
|
249
|
+
generatedTokens: generated.stats.numTokens,
|
|
250
250
|
maxNewTokens,
|
|
251
251
|
},
|
|
252
252
|
{jsonMode, toolsOffered: Boolean(tools), makeToolCallId: (index) => `local_${seed}_${index}`},
|
|
@@ -290,11 +290,10 @@ class LocalLlmProvider {
|
|
|
290
290
|
const template = new Template(tokenizerConfig.chatTemplate)
|
|
291
291
|
|
|
292
292
|
const startedAt = Date.now()
|
|
293
|
-
const
|
|
294
|
-
const runner = await createRunner(paths.model, paths.tokenizer, [])
|
|
293
|
+
const runner = await rne.llm.createLLMRunner(paths.model, paths.tokenizer, [])
|
|
295
294
|
let tokenizer: Tokenizer | undefined
|
|
296
295
|
try {
|
|
297
|
-
const maxSeqLen = runner.getKVCacheState().
|
|
296
|
+
const maxSeqLen = runner.getKVCacheState().maxContextLen
|
|
298
297
|
if (!Number.isInteger(maxSeqLen) || maxSeqLen <= CONTEXT_MARGIN_TOKENS + 16) {
|
|
299
298
|
throw new Error(`On-device model ${modelId} reported an invalid context size: ${maxSeqLen}`)
|
|
300
299
|
}
|
|
@@ -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,
|
|
@@ -6775,8 +6853,7 @@ class LocalMiniappRuntime {
|
|
|
6775
6853
|
|
|
6776
6854
|
/** A miniapp disconnected: as a host its placements go, as a provider its boxes are forgotten and its frames kept. */
|
|
6777
6855
|
private onWidgetPartyGone(packageName: string): void {
|
|
6778
|
-
|
|
6779
|
-
this.widgetPlacements = dropWidgetHost(this.widgetPlacements, packageName)
|
|
6856
|
+
this.releaseWidgetHost(packageName)
|
|
6780
6857
|
this.widgetPlacements = dropWidgetProvider(this.widgetPlacements, packageName)
|
|
6781
6858
|
miniappWidgetMode.setPlaced(packageName, false)
|
|
6782
6859
|
miniappWidgetMode.setWidgetOnly(packageName, false)
|
|
@@ -6791,8 +6868,13 @@ class LocalMiniappRuntime {
|
|
|
6791
6868
|
})
|
|
6792
6869
|
}
|
|
6793
6870
|
}
|
|
6871
|
+
}
|
|
6872
|
+
|
|
6873
|
+
public releaseWidgetHost(hostPackageName: string): void {
|
|
6874
|
+
const providersBefore = placedProviders(this.widgetPlacements)
|
|
6875
|
+
this.widgetPlacements = dropWidgetHost(this.widgetPlacements, hostPackageName)
|
|
6794
6876
|
for (const provider of providersBefore) {
|
|
6795
|
-
if (provider !==
|
|
6877
|
+
if (provider !== hostPackageName) void this.settleWidgetProvider(provider)
|
|
6796
6878
|
}
|
|
6797
6879
|
}
|
|
6798
6880
|
|
|
@@ -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
|
+
}
|
|
@@ -21,11 +21,12 @@ export function isCfwFirmwareVersion(firmwareVersion: string | null | undefined)
|
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Only models that ship a Glassly custom firmware, and only while they run it.
|
|
24
|
-
* Stock firmware
|
|
25
|
-
* intentionally excluded.
|
|
24
|
+
* Stock firmware and every other model are intentionally excluded.
|
|
26
25
|
*/
|
|
27
26
|
export function isCustomDashboardEligible(info: {model?: string | null; firmwareVersion?: string | null}): boolean {
|
|
28
27
|
if (!info.model) return false
|
|
28
|
+
// The NIMO drivers draw no scenes through its CFW yet; the dashboard would land on the text page.
|
|
29
|
+
if (info.model === DeviceTypes.NIMO) return false
|
|
29
30
|
if (info.model === DeviceTypes.MEMOMIND_ONE) {
|
|
30
31
|
const match = /^GLASSLYCFW\/([0-9]+)$/.exec(info.firmwareVersion ?? "")
|
|
31
32
|
const revision = match ? Number(match[1]) : 0
|
package/src/services/uiShim.ts
CHANGED
|
@@ -304,6 +304,13 @@ export function buildUiShim(options: UiShimOptions): string {
|
|
|
304
304
|
postEnvelope({ type: 'set_background', color: color });
|
|
305
305
|
}
|
|
306
306
|
|
|
307
|
+
var backHandler = null;
|
|
308
|
+
|
|
309
|
+
function setBackActionFn(handler) {
|
|
310
|
+
backHandler = typeof handler === 'function' ? handler : null;
|
|
311
|
+
postEnvelope({ type: 'set_back_action', shown: backHandler !== null });
|
|
312
|
+
}
|
|
313
|
+
|
|
307
314
|
function fireChannel(channel, payload) {
|
|
308
315
|
var list = channelHandlers[channel];
|
|
309
316
|
if (!list || list.length === 0) {
|
|
@@ -348,6 +355,12 @@ export function buildUiShim(options: UiShimOptions): string {
|
|
|
348
355
|
}
|
|
349
356
|
return;
|
|
350
357
|
}
|
|
358
|
+
if (type === 'back') {
|
|
359
|
+
if (backHandler) {
|
|
360
|
+
try { backHandler(); } catch (e) {}
|
|
361
|
+
}
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
351
364
|
if (type === 'cancel' && typeof envelope.requestId === 'string') {
|
|
352
365
|
// Background-side cancel (reserved; currently unused). If we had a
|
|
353
366
|
// future bidirectional RPC this would abort an in-flight UI handler.
|
|
@@ -366,6 +379,7 @@ export function buildUiShim(options: UiShimOptions): string {
|
|
|
366
379
|
onClose: onClose,
|
|
367
380
|
ready: readyFn,
|
|
368
381
|
setBackgroundColor: setBackgroundColorFn,
|
|
382
|
+
setBackAction: setBackActionFn,
|
|
369
383
|
/** @internal — packageName for diagnostic logs only. */
|
|
370
384
|
_packageName: packageName,
|
|
371
385
|
};
|
package/src/stores/settings.ts
CHANGED
|
@@ -132,13 +132,6 @@ export const SETTINGS: Record<string, Setting> = {
|
|
|
132
132
|
saveOnServer: true,
|
|
133
133
|
persist: true,
|
|
134
134
|
},
|
|
135
|
-
memomind_accessory_picker: {
|
|
136
|
-
key: "memomind_accessory_picker",
|
|
137
|
-
defaultValue: () => true,
|
|
138
|
-
writable: true,
|
|
139
|
-
saveOnServer: false,
|
|
140
|
-
persist: true,
|
|
141
|
-
},
|
|
142
135
|
debug_console: {
|
|
143
136
|
key: "debug_console",
|
|
144
137
|
defaultValue: () => false,
|
|
@@ -950,6 +943,13 @@ export const SETTINGS: Record<string, Setting> = {
|
|
|
950
943
|
saveOnServer: false,
|
|
951
944
|
persist: true,
|
|
952
945
|
},
|
|
946
|
+
debug_mirror_rail: {
|
|
947
|
+
key: "debug_mirror_rail",
|
|
948
|
+
defaultValue: () => false,
|
|
949
|
+
writable: true,
|
|
950
|
+
saveOnServer: false,
|
|
951
|
+
persist: true,
|
|
952
|
+
},
|
|
953
953
|
home_offline_card: {
|
|
954
954
|
key: "home_offline_card",
|
|
955
955
|
defaultValue: () => true,
|
|
@@ -1274,7 +1274,6 @@ export const BLUETOOTH_SETTING_KEYS: string[] = [
|
|
|
1274
1274
|
SETTINGS.cfw_scene_renderer.key,
|
|
1275
1275
|
SETTINGS.cfw_fast_ble.key,
|
|
1276
1276
|
SETTINGS.perf_logs.key,
|
|
1277
|
-
SETTINGS.memomind_accessory_picker.key,
|
|
1278
1277
|
SETTINGS.ring_via_glasses.key,
|
|
1279
1278
|
SETTINGS.display_max_fps.key,
|
|
1280
1279
|
SETTINGS.twelve_hour_time.key,
|