@glassly/engine 0.3.0-dev.460 → 0.3.0-dev.465
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 +3 -0
- package/build/engine.d.ts.map +1 -1
- package/build/facades/miniapps.d.ts +6 -0
- package/build/facades/miniapps.d.ts.map +1 -1
- package/build/facades/miniapps.js +7 -0
- package/build/facades/miniapps.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 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -1
- package/build/index.js.map +1 -1
- package/build/services/AppRegistry.js +1 -1
- package/build/services/AppRegistry.js.map +1 -1
- package/build/services/BlobStore.js +2 -2
- package/build/services/BlobStore.js.map +1 -1
- package/build/services/LocalMiniappRuntime.d.ts +22 -9
- package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
- package/build/services/LocalMiniappRuntime.js +104 -49
- package/build/services/LocalMiniappRuntime.js.map +1 -1
- package/build/services/MiniappLauncher.d.ts +2 -0
- package/build/services/MiniappLauncher.d.ts.map +1 -1
- package/build/services/MiniappLauncher.js +6 -1
- package/build/services/MiniappLauncher.js.map +1 -1
- package/build/services/miniappPermissions.d.ts +16 -0
- package/build/services/miniappPermissions.d.ts.map +1 -0
- package/build/services/miniappPermissions.js +7 -0
- package/build/services/miniappPermissions.js.map +1 -0
- package/build/services/pages/OverlayPages.d.ts +13 -2
- package/build/services/pages/OverlayPages.d.ts.map +1 -1
- package/build/services/pages/OverlayPages.js +11 -10
- package/build/services/pages/OverlayPages.js.map +1 -1
- package/build/services/pages/PageHost.d.ts +1 -1
- package/build/services/pages/PageHost.d.ts.map +1 -1
- package/build/services/pages/PageHost.js +2 -2
- package/build/services/pages/PageHost.js.map +1 -1
- package/build/services/pages/activeDashboard.d.ts +7 -0
- package/build/services/pages/activeDashboard.d.ts.map +1 -0
- package/build/services/pages/activeDashboard.js +21 -0
- package/build/services/pages/activeDashboard.js.map +1 -0
- package/build/services/pages/overlayApps.d.ts +20 -13
- package/build/services/pages/overlayApps.d.ts.map +1 -1
- package/build/services/pages/overlayApps.js +50 -31
- package/build/services/pages/overlayApps.js.map +1 -1
- package/build/services/settingsBridge.d.ts +15 -14
- package/build/services/settingsBridge.d.ts.map +1 -1
- package/build/services/settingsBridge.js +20 -20
- package/build/services/settingsBridge.js.map +1 -1
- package/build/stores/apps.d.ts +5 -0
- package/build/stores/apps.d.ts.map +1 -1
- package/build/stores/apps.js +40 -2
- package/build/stores/apps.js.map +1 -1
- package/build/stores/settings.d.ts.map +1 -1
- package/build/stores/settings.js +6 -45
- package/build/stores/settings.js.map +1 -1
- package/build/types/applet.d.ts +2 -2
- package/build/types/applet.d.ts.map +1 -1
- package/build/types/applet.js.map +1 -1
- package/package.json +7 -7
- package/src/facades/miniapps.ts +9 -0
- package/src/generated/releaseMetadata.ts +3 -3
- package/src/index.ts +2 -0
- package/src/services/AppRegistry.ts +1 -1
- package/src/services/BlobStore.ts +2 -2
- package/src/services/LocalMiniappRuntime.ts +124 -49
- package/src/services/MiniappLauncher.ts +6 -1
- package/src/services/miniappPermissions.ts +19 -0
- package/src/services/pages/OverlayPages.ts +11 -10
- package/src/services/pages/PageHost.ts +2 -2
- package/src/services/pages/activeDashboard.ts +24 -0
- package/src/services/pages/overlayApps.ts +51 -30
- package/src/services/settingsBridge.ts +20 -20
- package/src/stores/apps.ts +44 -2
- package/src/stores/settings.ts +6 -45
- package/src/types/applet.ts +3 -1
|
@@ -1,51 +1,71 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The overlay
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
2
|
+
* The overlay app: the connected miniapp that holds the dashboard role
|
|
3
|
+
* (ADR-0009). It alone may take the overlay slot through
|
|
4
|
+
* `session.display.showView("dashboard")` and has "dashboard" in its
|
|
5
|
+
* `display.views` capability while the glasses are eligible. The runtime
|
|
6
|
+
* records each dashboard miniapp (manifest type "dashboard") as it connects
|
|
7
|
+
* and stops, and who holds the role; the engine never knows what a dashboard
|
|
8
|
+
* draws. Kept free of imports so the runtime and the notification policy can
|
|
9
|
+
* both read it without a cycle.
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
12
|
export type DisplayView = "main" | "dashboard"
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
/** Connected dashboard miniapps, role or not. */
|
|
15
|
+
const connected = new Set<string>()
|
|
16
|
+
/** Connected dashboards listening to the phone's notifications. */
|
|
17
|
+
const listening = new Set<string>()
|
|
18
|
+
let active: string | null = null
|
|
16
19
|
const listeners = new Set<() => void>()
|
|
17
20
|
|
|
21
|
+
/** `packageName` holds the dashboard role and is connected. */
|
|
18
22
|
export function isOverlayApp(packageName: string): boolean {
|
|
19
|
-
return
|
|
23
|
+
return packageName === active && connected.has(packageName)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** `packageName` is a connected dashboard miniapp, whether or not it holds the role. */
|
|
27
|
+
export function isDashboardApp(packageName: string): boolean {
|
|
28
|
+
return connected.has(packageName)
|
|
20
29
|
}
|
|
21
30
|
|
|
22
31
|
export function overlayApps(): readonly string[] {
|
|
23
|
-
return [
|
|
32
|
+
return active !== null && connected.has(active) ? [active] : []
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The package that holds the dashboard role, connected or not. */
|
|
36
|
+
export function activeDashboard(): string | null {
|
|
37
|
+
return active
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function setActiveDashboard(packageName: string | null): void {
|
|
41
|
+
if (active === packageName) return
|
|
42
|
+
active = packageName
|
|
43
|
+
emit()
|
|
24
44
|
}
|
|
25
45
|
|
|
26
|
-
export function
|
|
27
|
-
if (
|
|
28
|
-
|
|
46
|
+
export function addDashboardApp(packageName: string): void {
|
|
47
|
+
if (connected.has(packageName)) return
|
|
48
|
+
connected.add(packageName)
|
|
29
49
|
emit()
|
|
30
50
|
}
|
|
31
51
|
|
|
32
|
-
export function
|
|
33
|
-
|
|
34
|
-
if (!
|
|
52
|
+
export function removeDashboardApp(packageName: string): void {
|
|
53
|
+
listening.delete(packageName)
|
|
54
|
+
if (!connected.delete(packageName)) return
|
|
35
55
|
emit()
|
|
36
56
|
}
|
|
37
57
|
|
|
38
|
-
/** Record whether
|
|
39
|
-
export function setOverlayViewer(packageName: string,
|
|
40
|
-
const was =
|
|
41
|
-
if (
|
|
42
|
-
else
|
|
43
|
-
if (was !==
|
|
58
|
+
/** Record whether a dashboard subscribes to phone notifications (the overlay app is the viewer while it does). */
|
|
59
|
+
export function setOverlayViewer(packageName: string, subscribed: boolean): void {
|
|
60
|
+
const was = overlayViewerRunning()
|
|
61
|
+
if (subscribed && connected.has(packageName)) listening.add(packageName)
|
|
62
|
+
else listening.delete(packageName)
|
|
63
|
+
if (was !== overlayViewerRunning()) emit()
|
|
44
64
|
}
|
|
45
65
|
|
|
46
|
-
/**
|
|
66
|
+
/** The overlay app presents phone notifications. */
|
|
47
67
|
export function overlayViewerRunning(): boolean {
|
|
48
|
-
return
|
|
68
|
+
return active !== null && connected.has(active) && listening.has(active)
|
|
49
69
|
}
|
|
50
70
|
|
|
51
71
|
export function onOverlayAppsChange(listener: () => void): () => void {
|
|
@@ -55,9 +75,9 @@ export function onOverlayAppsChange(listener: () => void): () => void {
|
|
|
55
75
|
}
|
|
56
76
|
}
|
|
57
77
|
|
|
58
|
-
/** The views `packageName` may draw: main for everyone, the dashboard for overlay
|
|
78
|
+
/** The views `packageName` may draw: main for everyone, the dashboard for the overlay app on eligible glasses. */
|
|
59
79
|
export function displayViewsFor(packageName: string, eligible: boolean): DisplayView[] {
|
|
60
|
-
return
|
|
80
|
+
return isOverlayApp(packageName) && eligible ? ["main", "dashboard"] : ["main"]
|
|
61
81
|
}
|
|
62
82
|
|
|
63
83
|
function emit(): void {
|
|
@@ -66,6 +86,7 @@ function emit(): void {
|
|
|
66
86
|
|
|
67
87
|
/** Tests only. */
|
|
68
88
|
export function _resetOverlayAppsForTest(): void {
|
|
69
|
-
|
|
70
|
-
|
|
89
|
+
connected.clear()
|
|
90
|
+
listening.clear()
|
|
91
|
+
active = null
|
|
71
92
|
}
|
|
@@ -1,28 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The settings bridge's vocabulary (session.settings): which engine settings
|
|
3
|
-
* a
|
|
4
|
-
* computes rather than stores, and the
|
|
5
|
-
* runtime's handlers stay thin and this can
|
|
6
|
-
* import surface the runtime pulls in.
|
|
3
|
+
* a miniapp with the PLATFORM_SETTINGS permission may read or write, the
|
|
4
|
+
* derived read-only keys the engine computes rather than stores, and the
|
|
5
|
+
* request validation. Pure, so the runtime's handlers stay thin and this can
|
|
6
|
+
* be tested without the native import surface the runtime pulls in.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* A dashboard miniapp is the usual consumer: it edits the notification and
|
|
9
|
+
* glasses-menu keys the phone's settings screens used to, through the store,
|
|
10
|
+
* so persistence, server sync and the Bluetooth sync of `menu_apps` behave as
|
|
11
|
+
* if a phone screen wrote them. A dashboard's own look is not here; it keeps
|
|
12
|
+
* that in its storage.
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
15
|
export type SettingsBridgeAccess = "read" | "write"
|
|
15
16
|
|
|
16
|
-
/** Stored settings the bridge serves, by key, with what a
|
|
17
|
+
/** Stored settings the bridge serves, by key, with what a permitted app may do to each. */
|
|
17
18
|
export const SETTINGS_BRIDGE_KEYS: Readonly<Record<string, SettingsBridgeAccess>> = {
|
|
18
19
|
// The dashboard's master switch is a phone-side debug knob.
|
|
19
20
|
custom_dashboard_enabled: "read",
|
|
20
|
-
custom_dashboard_clock_style: "write",
|
|
21
|
-
custom_dashboard_clock_show_date: "write",
|
|
22
|
-
custom_dashboard_battery_style: "write",
|
|
23
|
-
custom_dashboard_layout: "write",
|
|
24
|
-
custom_dashboard_calendar_event_count: "write",
|
|
25
|
-
custom_dashboard_calendar_days_ahead: "write",
|
|
26
21
|
contextual_dashboard: "write",
|
|
27
22
|
twelve_hour_time: "write",
|
|
28
23
|
menu_apps: "write",
|
|
@@ -38,18 +33,23 @@ export const SETTINGS_BRIDGE_KEYS: Readonly<Record<string, SettingsBridgeAccess>
|
|
|
38
33
|
|
|
39
34
|
/**
|
|
40
35
|
* Read-only keys the engine computes: whether the wearer can see the phone,
|
|
41
|
-
* whether the foreground miniapp is drawing on the glasses,
|
|
42
|
-
* battery
|
|
43
|
-
* stored keys.
|
|
36
|
+
* whether the foreground miniapp is drawing on the glasses, the ring's
|
|
37
|
+
* battery, and the package that holds the dashboard role (null for nobody).
|
|
38
|
+
* They change on their own clocks; the runtime pushes them like stored keys.
|
|
44
39
|
*/
|
|
45
|
-
export const DERIVED_SETTINGS_KEYS = [
|
|
40
|
+
export const DERIVED_SETTINGS_KEYS = [
|
|
41
|
+
"phone_active",
|
|
42
|
+
"foreground_app_on_screen",
|
|
43
|
+
"ring_battery",
|
|
44
|
+
"active_dashboard",
|
|
45
|
+
] as const
|
|
46
46
|
export type DerivedSettingsKey = (typeof DERIVED_SETTINGS_KEYS)[number]
|
|
47
47
|
|
|
48
48
|
export function isDerivedSettingsKey(key: string): key is DerivedSettingsKey {
|
|
49
49
|
return (DERIVED_SETTINGS_KEYS as readonly string[]).includes(key)
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
/** What a
|
|
52
|
+
/** What a permitted app may do with `key`, or null when the bridge does not serve it. */
|
|
53
53
|
export function settingsBridgeAccess(key: string): SettingsBridgeAccess | null {
|
|
54
54
|
if (isDerivedSettingsKey(key)) return "read"
|
|
55
55
|
return SETTINGS_BRIDGE_KEYS[key] ?? null
|
package/src/stores/apps.ts
CHANGED
|
@@ -25,6 +25,7 @@ import {islandNotifications} from "../services/NotificationsEmitter"
|
|
|
25
25
|
import sttModelManager from "../services/STTModelManager"
|
|
26
26
|
import {miniappLauncher} from "../services/MiniappLauncher"
|
|
27
27
|
import {miniappRunningRegistry} from "../services/MiniappRunningRegistry"
|
|
28
|
+
import {activeDashboardAmong, resolveActiveDashboard} from "../services/pages/activeDashboard"
|
|
28
29
|
import {defaultHiddenStatus, isGatedByDevTools, isGatedUntilPaired, resolveHiddenStatus} from "./appVisibility"
|
|
29
30
|
import {SETTINGS, useSettingsStore} from "./settings"
|
|
30
31
|
import {PermissionFeatures, permissions} from "../facades/permissions"
|
|
@@ -102,6 +103,7 @@ interface AppStatusState {
|
|
|
102
103
|
* off leaves it running. Persists the wearer's choice beside the hidden flag.
|
|
103
104
|
*/
|
|
104
105
|
setAlwaysOn: (packageName: string, status: boolean) => Promise<void>
|
|
106
|
+
setActiveDashboard: (packageName: string) => Promise<void>
|
|
105
107
|
/** Replace the whole apps array. Hosts use this when their extra source changes. */
|
|
106
108
|
setApps: (apps: ClientApp[]) => void
|
|
107
109
|
}
|
|
@@ -512,9 +514,17 @@ export const useAppStatusStore = create<AppStatusState>((set, get) => ({
|
|
|
512
514
|
|
|
513
515
|
uninstall: (packageName, version) => {
|
|
514
516
|
return Res.try_async(async () => {
|
|
517
|
+
const wasActiveDashboard = activeDashboardAmong(get().apps) === packageName
|
|
515
518
|
const res = await appRegistry.uninstall(packageName, version)
|
|
516
519
|
if (res.is_error()) throw res.error
|
|
517
520
|
set((s) => ({apps: s.apps.filter((a) => a.packageName !== packageName)}))
|
|
521
|
+
// The dashboard role fell back to another dashboard, which was stopped
|
|
522
|
+
// when it lost the role: start it, as launch would have.
|
|
523
|
+
if (wasActiveDashboard) {
|
|
524
|
+
const apps = get().apps
|
|
525
|
+
const heir = apps.find((a) => a.packageName === activeDashboardAmong(apps))
|
|
526
|
+
if (heir?.alwaysOn && !heir.running) await get().start(heir, {skipNavigation: true})
|
|
527
|
+
}
|
|
518
528
|
})
|
|
519
529
|
},
|
|
520
530
|
|
|
@@ -559,6 +569,18 @@ export const useAppStatusStore = create<AppStatusState>((set, get) => ({
|
|
|
559
569
|
if (status && !app.running) await get().start(app, {skipNavigation: true})
|
|
560
570
|
},
|
|
561
571
|
|
|
572
|
+
// The dashboard role (ADR-0009) moves to `packageName`: the previous holder
|
|
573
|
+
// stops and the new one starts in the background.
|
|
574
|
+
setActiveDashboard: async (packageName: string) => {
|
|
575
|
+
const apps = get().apps
|
|
576
|
+
const next = apps.find((a) => a.packageName === packageName && a.type === "dashboard")
|
|
577
|
+
if (!next) return
|
|
578
|
+
const previous = apps.find((a) => a.packageName === activeDashboardAmong(apps))
|
|
579
|
+
await useSettingsStore.getState().setSetting(SETTINGS.active_dashboard.key, packageName)
|
|
580
|
+
if (previous?.running && previous.packageName !== packageName) await get().stop(previous.packageName)
|
|
581
|
+
if (!next.running) await get().start(next, {skipNavigation: true})
|
|
582
|
+
},
|
|
583
|
+
|
|
562
584
|
setApps: (apps) => set({apps}),
|
|
563
585
|
}))
|
|
564
586
|
|
|
@@ -665,8 +687,28 @@ export const useRunningAlwaysOnApps = () => {
|
|
|
665
687
|
return useMemo(() => apps.filter((app) => app.alwaysOn && app.running), [apps])
|
|
666
688
|
}
|
|
667
689
|
|
|
668
|
-
/**
|
|
669
|
-
const
|
|
690
|
+
/** Installed miniapps that can hold the dashboard role: manifest type "dashboard". */
|
|
691
|
+
export const useDashboards = () => {
|
|
692
|
+
const apps = useApps()
|
|
693
|
+
return useMemo(() => apps.filter((app) => app.type === "dashboard"), [apps])
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
/** The miniapp that holds the dashboard role (ADR-0009), or null when none is installed. */
|
|
697
|
+
export const useActiveDashboard = (): ClientApp | null => {
|
|
698
|
+
const dashboards = useDashboards()
|
|
699
|
+
const chosen = useSettingsStore((s) => s.getSetting(SETTINGS.active_dashboard.key))
|
|
700
|
+
return useMemo(() => {
|
|
701
|
+
const packageName = resolveActiveDashboard(
|
|
702
|
+
dashboards.map((app) => app.packageName),
|
|
703
|
+
chosen,
|
|
704
|
+
SETTINGS.active_dashboard.defaultValue(),
|
|
705
|
+
)
|
|
706
|
+
return dashboards.find((app) => app.packageName === packageName) ?? null
|
|
707
|
+
}, [dashboards, chosen])
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/** Apps that run without a foreground surface: background ones and dashboards. */
|
|
711
|
+
const isBackgroundApp = (app: ClientApp): boolean => app.type === "background" || app.type === "dashboard"
|
|
670
712
|
|
|
671
713
|
export const useActiveBackgroundApps = () => {
|
|
672
714
|
const apps = useApps()
|
package/src/stores/settings.ts
CHANGED
|
@@ -794,51 +794,12 @@ export const SETTINGS: Record<string, Setting> = {
|
|
|
794
794
|
saveOnServer: false,
|
|
795
795
|
persist: true,
|
|
796
796
|
},
|
|
797
|
-
//
|
|
798
|
-
//
|
|
799
|
-
//
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
defaultValue: () => "compact",
|
|
804
|
-
writable: true,
|
|
805
|
-
saveOnServer: false,
|
|
806
|
-
persist: true,
|
|
807
|
-
},
|
|
808
|
-
custom_dashboard_clock_show_date: {
|
|
809
|
-
key: "custom_dashboard_clock_show_date",
|
|
810
|
-
defaultValue: () => true,
|
|
811
|
-
writable: true,
|
|
812
|
-
saveOnServer: false,
|
|
813
|
-
persist: true,
|
|
814
|
-
},
|
|
815
|
-
// "solid" | "two_tone": the battery widgets' G and R.
|
|
816
|
-
custom_dashboard_battery_style: {
|
|
817
|
-
key: "custom_dashboard_battery_style",
|
|
818
|
-
defaultValue: () => "solid",
|
|
819
|
-
writable: true,
|
|
820
|
-
saveOnServer: false,
|
|
821
|
-
persist: true,
|
|
822
|
-
},
|
|
823
|
-
// {preset, slots: {slotId: widgetId}, scratchpad: [widgetId]} — the
|
|
824
|
-
// dashboard miniapp owns the shape and its default; null draws its default layout.
|
|
825
|
-
custom_dashboard_layout: {
|
|
826
|
-
key: "custom_dashboard_layout",
|
|
827
|
-
defaultValue: () => null,
|
|
828
|
-
writable: true,
|
|
829
|
-
saveOnServer: false,
|
|
830
|
-
persist: true,
|
|
831
|
-
},
|
|
832
|
-
custom_dashboard_calendar_event_count: {
|
|
833
|
-
key: "custom_dashboard_calendar_event_count",
|
|
834
|
-
defaultValue: () => 3,
|
|
835
|
-
writable: true,
|
|
836
|
-
saveOnServer: false,
|
|
837
|
-
persist: true,
|
|
838
|
-
},
|
|
839
|
-
custom_dashboard_calendar_days_ahead: {
|
|
840
|
-
key: "custom_dashboard_calendar_days_ahead",
|
|
841
|
-
defaultValue: () => 5,
|
|
797
|
+
// The wearer's choice of dashboard miniapp (ADR-0009). Installs are
|
|
798
|
+
// per-phone, so the choice is device-local. The default is the bundled
|
|
799
|
+
// dashboard, which the role falls back to when the choice is not installed.
|
|
800
|
+
active_dashboard: {
|
|
801
|
+
key: "active_dashboard",
|
|
802
|
+
defaultValue: () => "com.glassly.dashboard",
|
|
842
803
|
writable: true,
|
|
843
804
|
saveOnServer: false,
|
|
844
805
|
persist: true,
|
package/src/types/applet.ts
CHANGED
|
@@ -10,7 +10,7 @@ import type {CompatibilityResult} from "../utils/hardware/hardware"
|
|
|
10
10
|
/**
|
|
11
11
|
* App execution model types
|
|
12
12
|
*/
|
|
13
|
-
export type AppletType = "standard" | "background" | "
|
|
13
|
+
export type AppletType = "standard" | "background" | "dashboard"
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Permission types that apps can request
|
|
@@ -29,6 +29,8 @@ export type AppPermissionType =
|
|
|
29
29
|
| "MEMORY"
|
|
30
30
|
| "AI"
|
|
31
31
|
| "SPEAKERS"
|
|
32
|
+
| "MANAGE_MINIAPPS"
|
|
33
|
+
| "PLATFORM_SETTINGS"
|
|
32
34
|
|
|
33
35
|
/**
|
|
34
36
|
* Permission object with type and description
|