@glassly/engine 0.3.0-dev.462 → 0.3.0-dev.469
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/LocalMiniappRuntime.d.ts +22 -9
- package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
- package/build/services/LocalMiniappRuntime.js +108 -50
- 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 +19 -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/LocalMiniappRuntime.ts +129 -50
- 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 +19 -45
- package/src/types/applet.ts +3 -1
|
@@ -114,6 +114,8 @@ declare class MiniappLauncher {
|
|
|
114
114
|
*
|
|
115
115
|
* Always On apps (the dashboard) start whether or not they were running
|
|
116
116
|
* when the app was last killed, and get a switcher card like any other app.
|
|
117
|
+
* A dashboard that does not hold the role (ADR-0009) is not started by its
|
|
118
|
+
* Always On flag.
|
|
117
119
|
* Both restore paths skip apps missing required OS permissions without
|
|
118
120
|
* prompting or clearing their saved running/Always On choice.
|
|
119
121
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MiniappLauncher.d.ts","sourceRoot":"","sources":["../../src/services/MiniappLauncher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AASH,OAA4B,EAAC,KAAK,wBAAwB,EAAC,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"MiniappLauncher.d.ts","sourceRoot":"","sources":["../../src/services/MiniappLauncher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AASH,OAA4B,EAAC,KAAK,wBAAwB,EAAC,MAAM,uBAAuB,CAAA;AAExF,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,kBAAkB,CAAA;AACzD,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,YAAY,CAAA;AAExC,UAAU,YAAY;IACpB,oEAAoE;IACpE,MAAM,EAAE,QAAQ,CAAA;CACjB;AAED,6EAA6E;AAC7E,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,+DAA+D;IAC/D,YAAY,CAAC,EAAE,mBAAmB,CAAA;CACnC;AAED,6EAA6E;AAC7E,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,mBAAmB,EAAE,MAAM,EAAE,CAAA;IAC7B,iBAAiB,CAAC,EAAE,wBAAwB,CAAA;IAC5C,yEAAyE;IACzE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB;AAED,uDAAuD;AACvD,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,cAAM,eAAe;IACnB,OAAO,CAAC,IAAI,CAA4B;IAExC;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA2C;IAEpE,uEAAuE;IACvE,SAAS,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI;IAInC,OAAO,CAAC,aAAa;IAOrB,yEAAyE;IACzE,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;IAIvC;;;;;;OAMG;IACG,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IA8G7F;;;;;;;;;;OAUG;IACG,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;IAwBpF,iFAAiF;YACnE,KAAK;IAkCnB;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACG,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC;IAkC7C;;;;OAIG;IACG,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,gBAAgB,SAAS,EAAE,KAAK,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAKzG,mDAAmD;IAC7C,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiB9C,OAAO,CAAC,YAAY;IAKpB,4EAA4E;IAC5E,OAAO,CAAC,cAAc;CASvB;AAED,eAAO,MAAM,eAAe,iBAAwB,CAAA"}
|
|
@@ -29,6 +29,7 @@ import { storage } from "../utils/storage/storage";
|
|
|
29
29
|
import appRegistry, { getLocalAppRunningState, saveLocalAppRunningState } from "./AppRegistry";
|
|
30
30
|
import devServerBridge from "./DevServerBridge";
|
|
31
31
|
import localMiniappRuntime from "./LocalMiniappRuntime";
|
|
32
|
+
import { activeDashboardAmong } from "./pages/activeDashboard";
|
|
32
33
|
class MiniappLauncher {
|
|
33
34
|
deps = null;
|
|
34
35
|
/**
|
|
@@ -245,18 +246,22 @@ class MiniappLauncher {
|
|
|
245
246
|
*
|
|
246
247
|
* Always On apps (the dashboard) start whether or not they were running
|
|
247
248
|
* when the app was last killed, and get a switcher card like any other app.
|
|
249
|
+
* A dashboard that does not hold the role (ADR-0009) is not started by its
|
|
250
|
+
* Always On flag.
|
|
248
251
|
* Both restore paths skip apps missing required OS permissions without
|
|
249
252
|
* prompting or clearing their saved running/Always On choice.
|
|
250
253
|
*/
|
|
251
254
|
async autostartLocalMiniapps() {
|
|
252
255
|
const apps = await appRegistry.getInstalledMiniapps();
|
|
256
|
+
const activeDashboard = activeDashboardAmong(apps);
|
|
253
257
|
for (const app of apps) {
|
|
254
258
|
// Only phone-hosted miniapps have a JSContext to re-spawn. Offline
|
|
255
259
|
// built-ins (`local:false, offline:true`) restore their native running
|
|
256
260
|
// state elsewhere and have no bundle to launch.
|
|
257
261
|
if (!app.local)
|
|
258
262
|
continue;
|
|
259
|
-
|
|
263
|
+
const alwaysOn = app.alwaysOn && (app.type !== "dashboard" || app.packageName === activeDashboard);
|
|
264
|
+
if (!alwaysOn && !getLocalAppRunningState(app.packageName))
|
|
260
265
|
continue;
|
|
261
266
|
if (this.isRunning(app.packageName))
|
|
262
267
|
continue;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MiniappLauncher.js","sourceRoot":"","sources":["../../src/services/MiniappLauncher.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * MiniappLauncher — headless launch/teardown of a local miniapp's background\n * JS context.\n *\n * This is the one piece of \"run a miniapp\" that used to live inside the\n * `LocalMiniappView` React component (resolve the bundle → read the manifest →\n * `spawnAndRegister`), triggered only when the Compositor mounted that\n * component on foreground. Pulling it into the runtime means:\n *\n * - `start()` can actually spawn an app even when nothing foregrounds it\n * (e.g. a system app launching another miniapp via `session.miniapps`).\n * - the action broker can wake a stopped target headlessly — no UI at all.\n * - the dev hot-reload respawn path and the WebView mount path share one\n * resolve+spawn recipe instead of three copies.\n *\n * The launcher is **headless**: no React, no RN components. The engine-owned\n * MiniappEngine hands it the {@link JSRouter} instance once during\n * engine construction. OEMs embedding the runtime drive miniapp lifecycle\n * through the engine facade and stores, not a separate launcher configure API.\n *\n * Boundary: the launcher owns the **background** context. WebView *rendering*\n * (the UI layer) stays with the host — the launcher just hands back the\n * resolved UI entry so the host can mount its `<WebView>`.\n */\n\nimport {File} from \"expo-file-system\"\n\nimport {permissions} from \"../facades/permissions\"\nimport {decideDevLaunchRoute} from \"../utils/devMiniappLaunch\"\nimport {storage} from \"../utils/storage/storage\"\nimport appRegistry, {getLocalAppRunningState, saveLocalAppRunningState} from \"./AppRegistry\"\nimport devServerBridge from \"./DevServerBridge\"\nimport localMiniappRuntime, {type InstalledMiniappManifest} from \"./LocalMiniappRuntime\"\nimport type {MiniappLaunchSource} from \"@glassly/miniapp\"\nimport type {JSRouter} from \"./JSRouter\"\n\ninterface LauncherDeps {\n /** The host-constructed router (needs the native Crust binding). */\n router: JSRouter\n}\n\n/** Hints the host may pass from a view's props to avoid re-deriving them. */\nexport interface LaunchHints {\n devUrl?: string\n version?: string\n devPort?: string\n /** Who started the app; reported to the miniapp on connect. */\n launchSource?: MiniappLaunchSource\n}\n\n/** Everything the launcher resolved for a package — bg source + UI entry. */\nexport interface ResolvedBundle {\n bgSource: string\n uiUri: string | null\n uiBaseDir: string | null\n declaredPermissions: string[]\n installedManifest?: InstalledMiniappManifest\n /** Set for dev miniapps (HTTP off the dev server); null for released. */\n devUrl: string | null\n devPort: number | null\n}\n\n/** What the host needs back to render the UI layer. */\nexport interface LaunchResult {\n uiUri: string | null\n uiBaseDir: string | null\n}\n\nclass MiniappLauncher {\n private deps: LauncherDeps | null = null\n\n /**\n * In-flight launches keyed by packageName. Both apps.ts start() and the\n * WebView mount call ensureRunning() before the first spawnAndRegister\n * resolves — without this, neither sees the package in registeredPackages()\n * yet and both spawn the same context (a clobber/double-spawn). Concurrent\n * callers share the same promise.\n */\n private readonly inFlight = new Map<string, Promise<LaunchResult>>()\n\n /** Wire the router in. Called once from MiniappEngine construction. */\n configure(deps: LauncherDeps): void {\n this.deps = deps\n }\n\n private requireRouter(): JSRouter {\n if (!this.deps) {\n throw new Error(\"MiniappLauncher not configured — ensureMiniappEngine() has not run\")\n }\n return this.deps.router\n }\n\n /** True iff the package's background JS context is currently spawned. */\n isRunning(packageName: string): boolean {\n return this.deps?.router.registeredPackages().includes(packageName) ?? false\n }\n\n /**\n * Resolve the background JS source + UI entry + declared permissions for a\n * package. Handles both dev (HTTP off the running dev server) and released\n * (file:// from the installed snapshot). Reads disk/network/storage; does\n * NOT spawn. Returns null when the bundle can't be resolved (dev server\n * unreachable, missing entry, no installed version).\n */\n async resolveBundle(packageName: string, hints?: LaunchHints): Promise<ResolvedBundle | null> {\n const devUrl = hints?.devUrl ?? this.storedDevUrl(packageName)\n\n // --- Dev: load directly off the local dev server over HTTP. ---\n if (devUrl) {\n const route = await decideDevLaunchRoute(packageName, devUrl)\n if (route.decision === \"offline\" || !route.manifest) return null\n const manifest = route.manifest\n const entry = manifest.entry as {background?: string; ui?: string} | undefined\n if (!entry?.background) return null\n\n // Use the host that actually answered — may differ from the stored IP\n // after a laptop Wi-Fi change (mDNS / Metro failover inside decideDevLaunchRoute).\n const base = route.resolvedUrl.replace(/\\/$/, \"\")\n // entry.* are bundle-root paths (dist/ stripped); the dev server serves\n // files relative to cwd, so prepend dist/.\n const bgUrl = `${base}/dist/${entry.background.replace(/^\\.?\\/+/, \"\")}`\n const uiUri = entry.ui ? `${base}/dist/${entry.ui.replace(/^\\.?\\/+/, \"\")}` : null\n\n const perms = manifest.permissions as Array<{type?: string} | string> | undefined\n const declaredPermissions = (perms ?? [])\n .map((p) => (typeof p === \"string\" ? p : p?.type))\n .filter((t): t is string => typeof t === \"string\")\n const installedManifest: InstalledMiniappManifest = {\n packageName: typeof manifest.packageName === \"string\" ? manifest.packageName : packageName,\n name: manifest.name,\n version: typeof manifest.version === \"string\" ? manifest.version : undefined,\n sdkVersion: typeof manifest.sdkVersion === \"string\" ? manifest.sdkVersion : undefined,\n minHostVersion: typeof manifest.minHostVersion === \"string\" ? manifest.minHostVersion : undefined,\n type: typeof manifest.type === \"string\" ? manifest.type : undefined,\n entry: manifest.entry as InstalledMiniappManifest[\"entry\"],\n permissions: manifest.permissions as InstalledMiniappManifest[\"permissions\"],\n hardwareRequirements: manifest.hardwareRequirements as InstalledMiniappManifest[\"hardwareRequirements\"],\n actions: manifest.actions as InstalledMiniappManifest[\"actions\"],\n }\n\n let bgSource: string\n try {\n const res = await fetch(bgUrl)\n if (!res.ok) return null\n bgSource = await res.text()\n } catch {\n return null\n }\n\n return {\n bgSource,\n uiUri,\n uiBaseDir: uiUri ? uiUri.replace(/\\/[^/]+$/, \"/\") : null,\n declaredPermissions,\n installedManifest,\n devUrl: route.resolvedUrl,\n devPort: this.resolveDevPort(hints?.devPort, packageName),\n }\n }\n\n // --- Released: resolve from the installed file:// snapshot. ---\n const version = hints?.version ?? (await appRegistry.getActiveVersion(packageName))\n if (!version) return null\n const entryPaths = appRegistry.getMiniappEntryPaths(packageName, version)\n if (!entryPaths?.background) return null\n\n const manifest = appRegistry.getMiniappManifest(packageName, version) as {\n packageName?: string\n name?: string\n version?: string\n sdkVersion?: string\n minHostVersion?: string\n type?: string\n entry?: {background?: string; ui?: string}\n permissions?: Array<{type: string; required?: boolean; description?: string}>\n hardwareRequirements?: Array<{type: string; level: string; description?: string}>\n actions?: Array<{id?: unknown; description?: unknown; parameters?: unknown; outputSchema?: unknown}>\n } | null\n const declaredPermissions = (manifest?.permissions ?? [])\n .map((p) => p.type)\n .filter((t): t is string => typeof t === \"string\")\n const installedManifest: InstalledMiniappManifest | undefined = manifest\n ? {\n packageName: manifest.packageName ?? packageName,\n name: manifest.name,\n version: manifest.version ?? version,\n sdkVersion: manifest.sdkVersion,\n minHostVersion: manifest.minHostVersion,\n type: manifest.type,\n entry: manifest.entry,\n permissions: manifest.permissions,\n hardwareRequirements: manifest.hardwareRequirements,\n actions: manifest.actions,\n }\n : undefined\n\n let bgSource: string\n try {\n bgSource = new File(entryPaths.background).textSync()\n } catch {\n return null\n }\n\n return {\n bgSource,\n uiUri: entryPaths.ui ?? null,\n uiBaseDir: entryPaths.ui ? entryPaths.ui.replace(/\\/[^/]+$/, \"/\") : null,\n declaredPermissions,\n installedManifest,\n devUrl: null,\n devPort: null,\n }\n }\n\n /**\n * Ensure a miniapp's background JS context is spawned. Idempotent — if the\n * context is already alive this is a no-op spawn-wise (it still resolves the\n * UI entry so a re-mounting host gets its `<WebView>` source back).\n *\n * Resolves once the context is SPAWNED. It does NOT block on the context's\n * CONNECT handshake — the host's WebView mount shouldn't wait on background\n * JS. Callers that need the context to be connected and ready to receive\n * messages (the action broker) follow up with\n * {@link LocalMiniappRuntime.waitForConnect}.\n */\n async ensureRunning(packageName: string, hints?: LaunchHints): Promise<LaunchResult> {\n const router = this.requireRouter()\n\n // Already spawned: best-effort resolve for the UI entry, never throw —\n // a headless caller that doesn't need UI gets {null, null} fast even if\n // the dev server has since dropped.\n if (router.registeredPackages().includes(packageName)) {\n const existing = await this.resolveBundle(packageName, hints).catch(() => null)\n return {uiUri: existing?.uiUri ?? null, uiBaseDir: existing?.uiBaseDir ?? null}\n }\n\n // Coalesce concurrent launches of the same package onto one promise.\n const pending = this.inFlight.get(packageName)\n if (pending) return pending\n\n const launch = this.spawn(packageName, hints)\n this.inFlight.set(packageName, launch)\n try {\n return await launch\n } finally {\n this.inFlight.delete(packageName)\n }\n }\n\n /** Resolve the bundle and spawn the context. Serialized via {@link inFlight}. */\n private async spawn(packageName: string, hints?: LaunchHints): Promise<LaunchResult> {\n const router = this.requireRouter()\n const resolved = await this.resolveBundle(packageName, hints)\n if (!resolved) {\n throw new Error(`MiniappLauncher: cannot resolve bundle for ${packageName}`)\n }\n\n // Re-check after the async resolve: a different path may have spawned it\n // while we were fetching/reading the bundle.\n if (!router.registeredPackages().includes(packageName)) {\n const ok = await router.spawnAndRegister(packageName, resolved.bgSource, {\n permissions: resolved.declaredPermissions,\n installedManifest: resolved.installedManifest,\n launchSource: hints?.launchSource,\n })\n if (!ok) {\n throw new Error(`MiniappLauncher: spawn failed for ${packageName}`)\n }\n\n // Dev: wire the hot-reload + log-forwarding sidecar. No-op in prod\n // (devServerBridge silently drops when no sidecar is up).\n if (resolved.devUrl && resolved.devPort != null) {\n devServerBridge.connect(packageName, resolved.devUrl, resolved.devPort)\n } else if (resolved.devUrl) {\n // Dev miniapp with no resolvable port — it still launches, but hot\n // reload + log forwarding won't connect. Surface it instead of failing\n // silently (the old inline path hard-failed with \"no dev port\").\n console.warn(`MiniappLauncher: ${packageName} has no dev port — hot reload + log forwarding disabled`)\n }\n }\n\n return {uiUri: resolved.uiUri, uiBaseDir: resolved.uiBaseDir}\n }\n\n /**\n * Re-spawn local miniapps that were running when the app was last killed.\n *\n * Miniapps run in the phone's own JS engine, so a host-process kill tears\n * down their JSContext with nothing to resurrect them. Without this they\n * would silently stay stopped on the next launch even though the user left\n * them running.\n *\n * Each local miniapp persists its running flag to disk on start/stop\n * (`saveLocalAppRunningState`, via the applet's `onStart`/`onStop`). We read\n * those flags back and re-spawn the background context headlessly (no\n * foreground, no navigation) — the spawn registers the package so the home\n * tray/switcher project it as running, exactly as a normal start would. The\n * WebView, if any, re-attaches lazily when the user opens the app.\n *\n * Idempotent and best-effort: skips already-spawned contexts, and clears the\n * persisted flag for any app whose bundle can no longer be resolved\n * (uninstalled, or dev server gone) so a dead entry doesn't retry every boot.\n * Not compatibility-gated: a previously-running background app shouldn't be\n * dropped just because glasses are momentarily disconnected at boot — it\n * resumes when they reconnect, same as mid-session.\n *\n * Always On apps (the dashboard) start whether or not they were running\n * when the app was last killed, and get a switcher card like any other app.\n * Both restore paths skip apps missing required OS permissions without\n * prompting or clearing their saved running/Always On choice.\n */\n async autostartLocalMiniapps(): Promise<void> {\n const apps = await appRegistry.getInstalledMiniapps()\n for (const app of apps) {\n // Only phone-hosted miniapps have a JSContext to re-spawn. Offline\n // built-ins (`local:false, offline:true`) restore their native running\n // state elsewhere and have no bundle to launch.\n if (!app.local) continue\n if (!app.alwaysOn && !getLocalAppRunningState(app.packageName)) continue\n if (this.isRunning(app.packageName)) continue\n try {\n const missing = await permissions.missingRequired(app.permissions ?? [])\n if (missing.length > 0) {\n console.info(\n `MiniappLauncher: skipping autostart for ${app.packageName} — missing required permissions: ${missing.join(\n \", \",\n )}`,\n )\n continue\n }\n } catch (e) {\n console.warn(`MiniappLauncher: skipping autostart for ${app.packageName} — permission check failed`, e)\n continue\n }\n try {\n await this.ensureRunning(app.packageName)\n } catch (e) {\n console.warn(`MiniappLauncher: autostart failed for ${app.packageName} — clearing stale running flag`, e)\n saveLocalAppRunningState(app.packageName, false)\n }\n }\n }\n\n /**\n * Ensure the context is running AND has completed its CONNECT handshake.\n * Used by the action broker, which must not deliver to a context that hasn't\n * come up yet. Throws on spawn failure or connect timeout.\n */\n async ensureConnected(packageName: string, connectTimeoutMs = 10_000, hints?: LaunchHints): Promise<void> {\n await this.ensureRunning(packageName, hints)\n await localMiniappRuntime.waitForConnect(packageName, connectTimeoutMs)\n }\n\n /** Tear down a miniapp's background JS context. */\n async stop(packageName: string): Promise<void> {\n // If a launch is in flight, let it finish first. Otherwise unregister()\n // no-ops on the not-yet-registered package and the pending spawn then\n // re-registers the context — leaving it running despite the stop.\n const pending = this.inFlight.get(packageName)\n if (pending) {\n try {\n await pending\n } catch {\n // Launch failed — nothing got registered, so nothing to tear down.\n }\n }\n await this.deps?.router.unregister(packageName)\n }\n\n // --- internals -----------------------------------------------------------\n\n private storedDevUrl(packageName: string): string | undefined {\n const r = storage.load<string>(`${packageName}_dev_url`)\n return r.is_ok() ? r.value : undefined\n }\n\n /** resolveDevPort — moved verbatim from the old LocalMiniappView helper. */\n private resolveDevPort(searchParam: string | undefined, packageName: string): number | null {\n if (searchParam) {\n const n = parseInt(searchParam, 10)\n if (Number.isFinite(n)) return n\n }\n const stored = storage.load<number>(`${packageName}_dev_port`)\n if (stored.is_ok()) return stored.value\n return null\n }\n}\n\nexport const miniappLauncher = new MiniappLauncher()\n"]}
|
|
1
|
+
{"version":3,"file":"MiniappLauncher.js","sourceRoot":"","sources":["../../src/services/MiniappLauncher.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * MiniappLauncher — headless launch/teardown of a local miniapp's background\n * JS context.\n *\n * This is the one piece of \"run a miniapp\" that used to live inside the\n * `LocalMiniappView` React component (resolve the bundle → read the manifest →\n * `spawnAndRegister`), triggered only when the Compositor mounted that\n * component on foreground. Pulling it into the runtime means:\n *\n * - `start()` can actually spawn an app even when nothing foregrounds it\n * (e.g. a system app launching another miniapp via `session.miniapps`).\n * - the action broker can wake a stopped target headlessly — no UI at all.\n * - the dev hot-reload respawn path and the WebView mount path share one\n * resolve+spawn recipe instead of three copies.\n *\n * The launcher is **headless**: no React, no RN components. The engine-owned\n * MiniappEngine hands it the {@link JSRouter} instance once during\n * engine construction. OEMs embedding the runtime drive miniapp lifecycle\n * through the engine facade and stores, not a separate launcher configure API.\n *\n * Boundary: the launcher owns the **background** context. WebView *rendering*\n * (the UI layer) stays with the host — the launcher just hands back the\n * resolved UI entry so the host can mount its `<WebView>`.\n */\n\nimport {File} from \"expo-file-system\"\n\nimport {permissions} from \"../facades/permissions\"\nimport {decideDevLaunchRoute} from \"../utils/devMiniappLaunch\"\nimport {storage} from \"../utils/storage/storage\"\nimport appRegistry, {getLocalAppRunningState, saveLocalAppRunningState} from \"./AppRegistry\"\nimport devServerBridge from \"./DevServerBridge\"\nimport localMiniappRuntime, {type InstalledMiniappManifest} from \"./LocalMiniappRuntime\"\nimport {activeDashboardAmong} from \"./pages/activeDashboard\"\nimport type {MiniappLaunchSource} from \"@glassly/miniapp\"\nimport type {JSRouter} from \"./JSRouter\"\n\ninterface LauncherDeps {\n /** The host-constructed router (needs the native Crust binding). */\n router: JSRouter\n}\n\n/** Hints the host may pass from a view's props to avoid re-deriving them. */\nexport interface LaunchHints {\n devUrl?: string\n version?: string\n devPort?: string\n /** Who started the app; reported to the miniapp on connect. */\n launchSource?: MiniappLaunchSource\n}\n\n/** Everything the launcher resolved for a package — bg source + UI entry. */\nexport interface ResolvedBundle {\n bgSource: string\n uiUri: string | null\n uiBaseDir: string | null\n declaredPermissions: string[]\n installedManifest?: InstalledMiniappManifest\n /** Set for dev miniapps (HTTP off the dev server); null for released. */\n devUrl: string | null\n devPort: number | null\n}\n\n/** What the host needs back to render the UI layer. */\nexport interface LaunchResult {\n uiUri: string | null\n uiBaseDir: string | null\n}\n\nclass MiniappLauncher {\n private deps: LauncherDeps | null = null\n\n /**\n * In-flight launches keyed by packageName. Both apps.ts start() and the\n * WebView mount call ensureRunning() before the first spawnAndRegister\n * resolves — without this, neither sees the package in registeredPackages()\n * yet and both spawn the same context (a clobber/double-spawn). Concurrent\n * callers share the same promise.\n */\n private readonly inFlight = new Map<string, Promise<LaunchResult>>()\n\n /** Wire the router in. Called once from MiniappEngine construction. */\n configure(deps: LauncherDeps): void {\n this.deps = deps\n }\n\n private requireRouter(): JSRouter {\n if (!this.deps) {\n throw new Error(\"MiniappLauncher not configured — ensureMiniappEngine() has not run\")\n }\n return this.deps.router\n }\n\n /** True iff the package's background JS context is currently spawned. */\n isRunning(packageName: string): boolean {\n return this.deps?.router.registeredPackages().includes(packageName) ?? false\n }\n\n /**\n * Resolve the background JS source + UI entry + declared permissions for a\n * package. Handles both dev (HTTP off the running dev server) and released\n * (file:// from the installed snapshot). Reads disk/network/storage; does\n * NOT spawn. Returns null when the bundle can't be resolved (dev server\n * unreachable, missing entry, no installed version).\n */\n async resolveBundle(packageName: string, hints?: LaunchHints): Promise<ResolvedBundle | null> {\n const devUrl = hints?.devUrl ?? this.storedDevUrl(packageName)\n\n // --- Dev: load directly off the local dev server over HTTP. ---\n if (devUrl) {\n const route = await decideDevLaunchRoute(packageName, devUrl)\n if (route.decision === \"offline\" || !route.manifest) return null\n const manifest = route.manifest\n const entry = manifest.entry as {background?: string; ui?: string} | undefined\n if (!entry?.background) return null\n\n // Use the host that actually answered — may differ from the stored IP\n // after a laptop Wi-Fi change (mDNS / Metro failover inside decideDevLaunchRoute).\n const base = route.resolvedUrl.replace(/\\/$/, \"\")\n // entry.* are bundle-root paths (dist/ stripped); the dev server serves\n // files relative to cwd, so prepend dist/.\n const bgUrl = `${base}/dist/${entry.background.replace(/^\\.?\\/+/, \"\")}`\n const uiUri = entry.ui ? `${base}/dist/${entry.ui.replace(/^\\.?\\/+/, \"\")}` : null\n\n const perms = manifest.permissions as Array<{type?: string} | string> | undefined\n const declaredPermissions = (perms ?? [])\n .map((p) => (typeof p === \"string\" ? p : p?.type))\n .filter((t): t is string => typeof t === \"string\")\n const installedManifest: InstalledMiniappManifest = {\n packageName: typeof manifest.packageName === \"string\" ? manifest.packageName : packageName,\n name: manifest.name,\n version: typeof manifest.version === \"string\" ? manifest.version : undefined,\n sdkVersion: typeof manifest.sdkVersion === \"string\" ? manifest.sdkVersion : undefined,\n minHostVersion: typeof manifest.minHostVersion === \"string\" ? manifest.minHostVersion : undefined,\n type: typeof manifest.type === \"string\" ? manifest.type : undefined,\n entry: manifest.entry as InstalledMiniappManifest[\"entry\"],\n permissions: manifest.permissions as InstalledMiniappManifest[\"permissions\"],\n hardwareRequirements: manifest.hardwareRequirements as InstalledMiniappManifest[\"hardwareRequirements\"],\n actions: manifest.actions as InstalledMiniappManifest[\"actions\"],\n }\n\n let bgSource: string\n try {\n const res = await fetch(bgUrl)\n if (!res.ok) return null\n bgSource = await res.text()\n } catch {\n return null\n }\n\n return {\n bgSource,\n uiUri,\n uiBaseDir: uiUri ? uiUri.replace(/\\/[^/]+$/, \"/\") : null,\n declaredPermissions,\n installedManifest,\n devUrl: route.resolvedUrl,\n devPort: this.resolveDevPort(hints?.devPort, packageName),\n }\n }\n\n // --- Released: resolve from the installed file:// snapshot. ---\n const version = hints?.version ?? (await appRegistry.getActiveVersion(packageName))\n if (!version) return null\n const entryPaths = appRegistry.getMiniappEntryPaths(packageName, version)\n if (!entryPaths?.background) return null\n\n const manifest = appRegistry.getMiniappManifest(packageName, version) as {\n packageName?: string\n name?: string\n version?: string\n sdkVersion?: string\n minHostVersion?: string\n type?: string\n entry?: {background?: string; ui?: string}\n permissions?: Array<{type: string; required?: boolean; description?: string}>\n hardwareRequirements?: Array<{type: string; level: string; description?: string}>\n actions?: Array<{id?: unknown; description?: unknown; parameters?: unknown; outputSchema?: unknown}>\n } | null\n const declaredPermissions = (manifest?.permissions ?? [])\n .map((p) => p.type)\n .filter((t): t is string => typeof t === \"string\")\n const installedManifest: InstalledMiniappManifest | undefined = manifest\n ? {\n packageName: manifest.packageName ?? packageName,\n name: manifest.name,\n version: manifest.version ?? version,\n sdkVersion: manifest.sdkVersion,\n minHostVersion: manifest.minHostVersion,\n type: manifest.type,\n entry: manifest.entry,\n permissions: manifest.permissions,\n hardwareRequirements: manifest.hardwareRequirements,\n actions: manifest.actions,\n }\n : undefined\n\n let bgSource: string\n try {\n bgSource = new File(entryPaths.background).textSync()\n } catch {\n return null\n }\n\n return {\n bgSource,\n uiUri: entryPaths.ui ?? null,\n uiBaseDir: entryPaths.ui ? entryPaths.ui.replace(/\\/[^/]+$/, \"/\") : null,\n declaredPermissions,\n installedManifest,\n devUrl: null,\n devPort: null,\n }\n }\n\n /**\n * Ensure a miniapp's background JS context is spawned. Idempotent — if the\n * context is already alive this is a no-op spawn-wise (it still resolves the\n * UI entry so a re-mounting host gets its `<WebView>` source back).\n *\n * Resolves once the context is SPAWNED. It does NOT block on the context's\n * CONNECT handshake — the host's WebView mount shouldn't wait on background\n * JS. Callers that need the context to be connected and ready to receive\n * messages (the action broker) follow up with\n * {@link LocalMiniappRuntime.waitForConnect}.\n */\n async ensureRunning(packageName: string, hints?: LaunchHints): Promise<LaunchResult> {\n const router = this.requireRouter()\n\n // Already spawned: best-effort resolve for the UI entry, never throw —\n // a headless caller that doesn't need UI gets {null, null} fast even if\n // the dev server has since dropped.\n if (router.registeredPackages().includes(packageName)) {\n const existing = await this.resolveBundle(packageName, hints).catch(() => null)\n return {uiUri: existing?.uiUri ?? null, uiBaseDir: existing?.uiBaseDir ?? null}\n }\n\n // Coalesce concurrent launches of the same package onto one promise.\n const pending = this.inFlight.get(packageName)\n if (pending) return pending\n\n const launch = this.spawn(packageName, hints)\n this.inFlight.set(packageName, launch)\n try {\n return await launch\n } finally {\n this.inFlight.delete(packageName)\n }\n }\n\n /** Resolve the bundle and spawn the context. Serialized via {@link inFlight}. */\n private async spawn(packageName: string, hints?: LaunchHints): Promise<LaunchResult> {\n const router = this.requireRouter()\n const resolved = await this.resolveBundle(packageName, hints)\n if (!resolved) {\n throw new Error(`MiniappLauncher: cannot resolve bundle for ${packageName}`)\n }\n\n // Re-check after the async resolve: a different path may have spawned it\n // while we were fetching/reading the bundle.\n if (!router.registeredPackages().includes(packageName)) {\n const ok = await router.spawnAndRegister(packageName, resolved.bgSource, {\n permissions: resolved.declaredPermissions,\n installedManifest: resolved.installedManifest,\n launchSource: hints?.launchSource,\n })\n if (!ok) {\n throw new Error(`MiniappLauncher: spawn failed for ${packageName}`)\n }\n\n // Dev: wire the hot-reload + log-forwarding sidecar. No-op in prod\n // (devServerBridge silently drops when no sidecar is up).\n if (resolved.devUrl && resolved.devPort != null) {\n devServerBridge.connect(packageName, resolved.devUrl, resolved.devPort)\n } else if (resolved.devUrl) {\n // Dev miniapp with no resolvable port — it still launches, but hot\n // reload + log forwarding won't connect. Surface it instead of failing\n // silently (the old inline path hard-failed with \"no dev port\").\n console.warn(`MiniappLauncher: ${packageName} has no dev port — hot reload + log forwarding disabled`)\n }\n }\n\n return {uiUri: resolved.uiUri, uiBaseDir: resolved.uiBaseDir}\n }\n\n /**\n * Re-spawn local miniapps that were running when the app was last killed.\n *\n * Miniapps run in the phone's own JS engine, so a host-process kill tears\n * down their JSContext with nothing to resurrect them. Without this they\n * would silently stay stopped on the next launch even though the user left\n * them running.\n *\n * Each local miniapp persists its running flag to disk on start/stop\n * (`saveLocalAppRunningState`, via the applet's `onStart`/`onStop`). We read\n * those flags back and re-spawn the background context headlessly (no\n * foreground, no navigation) — the spawn registers the package so the home\n * tray/switcher project it as running, exactly as a normal start would. The\n * WebView, if any, re-attaches lazily when the user opens the app.\n *\n * Idempotent and best-effort: skips already-spawned contexts, and clears the\n * persisted flag for any app whose bundle can no longer be resolved\n * (uninstalled, or dev server gone) so a dead entry doesn't retry every boot.\n * Not compatibility-gated: a previously-running background app shouldn't be\n * dropped just because glasses are momentarily disconnected at boot — it\n * resumes when they reconnect, same as mid-session.\n *\n * Always On apps (the dashboard) start whether or not they were running\n * when the app was last killed, and get a switcher card like any other app.\n * A dashboard that does not hold the role (ADR-0009) is not started by its\n * Always On flag.\n * Both restore paths skip apps missing required OS permissions without\n * prompting or clearing their saved running/Always On choice.\n */\n async autostartLocalMiniapps(): Promise<void> {\n const apps = await appRegistry.getInstalledMiniapps()\n const activeDashboard = activeDashboardAmong(apps)\n for (const app of apps) {\n // Only phone-hosted miniapps have a JSContext to re-spawn. Offline\n // built-ins (`local:false, offline:true`) restore their native running\n // state elsewhere and have no bundle to launch.\n if (!app.local) continue\n const alwaysOn = app.alwaysOn && (app.type !== \"dashboard\" || app.packageName === activeDashboard)\n if (!alwaysOn && !getLocalAppRunningState(app.packageName)) continue\n if (this.isRunning(app.packageName)) continue\n try {\n const missing = await permissions.missingRequired(app.permissions ?? [])\n if (missing.length > 0) {\n console.info(\n `MiniappLauncher: skipping autostart for ${app.packageName} — missing required permissions: ${missing.join(\n \", \",\n )}`,\n )\n continue\n }\n } catch (e) {\n console.warn(`MiniappLauncher: skipping autostart for ${app.packageName} — permission check failed`, e)\n continue\n }\n try {\n await this.ensureRunning(app.packageName)\n } catch (e) {\n console.warn(`MiniappLauncher: autostart failed for ${app.packageName} — clearing stale running flag`, e)\n saveLocalAppRunningState(app.packageName, false)\n }\n }\n }\n\n /**\n * Ensure the context is running AND has completed its CONNECT handshake.\n * Used by the action broker, which must not deliver to a context that hasn't\n * come up yet. Throws on spawn failure or connect timeout.\n */\n async ensureConnected(packageName: string, connectTimeoutMs = 10_000, hints?: LaunchHints): Promise<void> {\n await this.ensureRunning(packageName, hints)\n await localMiniappRuntime.waitForConnect(packageName, connectTimeoutMs)\n }\n\n /** Tear down a miniapp's background JS context. */\n async stop(packageName: string): Promise<void> {\n // If a launch is in flight, let it finish first. Otherwise unregister()\n // no-ops on the not-yet-registered package and the pending spawn then\n // re-registers the context — leaving it running despite the stop.\n const pending = this.inFlight.get(packageName)\n if (pending) {\n try {\n await pending\n } catch {\n // Launch failed — nothing got registered, so nothing to tear down.\n }\n }\n await this.deps?.router.unregister(packageName)\n }\n\n // --- internals -----------------------------------------------------------\n\n private storedDevUrl(packageName: string): string | undefined {\n const r = storage.load<string>(`${packageName}_dev_url`)\n return r.is_ok() ? r.value : undefined\n }\n\n /** resolveDevPort — moved verbatim from the old LocalMiniappView helper. */\n private resolveDevPort(searchParam: string | undefined, packageName: string): number | null {\n if (searchParam) {\n const n = parseInt(searchParam, 10)\n if (Number.isFinite(n)) return n\n }\n const stored = storage.load<number>(`${packageName}_dev_port`)\n if (stored.is_ok()) return stored.value\n return null\n }\n}\n\nexport const miniappLauncher = new MiniappLauncher()\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a miniapp may use an API gated on a manifest permission
|
|
3
|
+
* (miniapp.json -> permissions): `session.miniapps` needs MANAGE_MINIAPPS,
|
|
4
|
+
* `session.settings` PLATFORM_SETTINGS, the notification app list
|
|
5
|
+
* READ_NOTIFICATIONS. System apps pass without declaring it. Pure, so the
|
|
6
|
+
* runtime only does the reply.
|
|
7
|
+
*/
|
|
8
|
+
export interface PermissionCaller {
|
|
9
|
+
system: boolean;
|
|
10
|
+
permissions?: ReadonlyArray<{
|
|
11
|
+
type?: string;
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
14
|
+
export declare function declaresPermission(permissions: PermissionCaller["permissions"], permission: string): boolean;
|
|
15
|
+
export declare function mayUsePermission(caller: PermissionCaller, permission: string): boolean;
|
|
16
|
+
//# sourceMappingURL=miniappPermissions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"miniappPermissions.d.ts","sourceRoot":"","sources":["../../src/services/miniappPermissions.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,OAAO,CAAA;IACf,WAAW,CAAC,EAAE,aAAa,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAC,CAAC,CAAA;CAC7C;AAED,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,gBAAgB,CAAC,aAAa,CAAC,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAE5G;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAEtF"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function declaresPermission(permissions, permission) {
|
|
2
|
+
return (permissions ?? []).some((declared) => declared.type?.toUpperCase() === permission);
|
|
3
|
+
}
|
|
4
|
+
export function mayUsePermission(caller, permission) {
|
|
5
|
+
return caller.system || declaresPermission(caller.permissions, permission);
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=miniappPermissions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"miniappPermissions.js","sourceRoot":"","sources":["../../src/services/miniappPermissions.ts"],"names":[],"mappings":"AAYA,MAAM,UAAU,kBAAkB,CAAC,WAA4C,EAAE,UAAkB;IACjG,OAAO,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK,UAAU,CAAC,CAAA;AAC5F,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,MAAwB,EAAE,UAAkB;IAC3E,OAAO,MAAM,CAAC,MAAM,IAAI,kBAAkB,CAAC,MAAM,CAAC,WAAW,EAAE,UAAU,CAAC,CAAA;AAC5E,CAAC","sourcesContent":["/**\n * Whether a miniapp may use an API gated on a manifest permission\n * (miniapp.json -> permissions): `session.miniapps` needs MANAGE_MINIAPPS,\n * `session.settings` PLATFORM_SETTINGS, the notification app list\n * READ_NOTIFICATIONS. System apps pass without declaring it. Pure, so the\n * runtime only does the reply.\n */\nexport interface PermissionCaller {\n system: boolean\n permissions?: ReadonlyArray<{type?: string}>\n}\n\nexport function declaresPermission(permissions: PermissionCaller[\"permissions\"], permission: string): boolean {\n return (permissions ?? []).some((declared) => declared.type?.toUpperCase() === permission)\n}\n\nexport function mayUsePermission(caller: PermissionCaller, permission: string): boolean {\n return caller.system || declaresPermission(caller.permissions, permission)\n}\n"]}
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The overlay app's page (ADR-0006, ADR-0009): one Page per dashboard that
|
|
3
|
+
* has taken the slot, the only Page implementation the page host shows. The
|
|
4
|
+
* engine keeps the slot and the eligibility watch; a page only relays:
|
|
5
|
+
* holding the slot is reported to its app as a view change, and the app's
|
|
6
|
+
* frames reach the slot through `renderOverlay` under its package name. Only
|
|
7
|
+
* the dashboard that holds the role may show; when the role moves, the
|
|
8
|
+
* previous holder hears it was replaced. The runtime supplies the sink that
|
|
9
|
+
* delivers to apps, so this module never imports it.
|
|
10
|
+
*/
|
|
11
|
+
import { type PageHiddenReason } from "./PageHost";
|
|
1
12
|
export type DashboardViewChangeReason = "shown" | "hidden" | "replaced" | "ineligible";
|
|
2
13
|
export interface OverlayPagesSink {
|
|
3
14
|
/** Deliver a display_view event to the app. */
|
|
@@ -13,8 +24,8 @@ declare class OverlayPages {
|
|
|
13
24
|
get showingPackage(): string | null;
|
|
14
25
|
/** Give the app the slot (a no-op when it already shows or pages are not eligible). */
|
|
15
26
|
show(packageName: string): boolean;
|
|
16
|
-
/** Release the slot if the app holds it. */
|
|
17
|
-
hide(packageName: string): void;
|
|
27
|
+
/** Release the slot if the app holds it; "replaced" when the dashboard role moved on. */
|
|
28
|
+
hide(packageName: string, reason?: PageHiddenReason): void;
|
|
18
29
|
/** The app stopped: release the slot without telling it, and forget its page. */
|
|
19
30
|
remove(packageName: string): void;
|
|
20
31
|
private pageFor;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OverlayPages.d.ts","sourceRoot":"","sources":["../../../src/services/pages/OverlayPages.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"OverlayPages.d.ts","sourceRoot":"","sources":["../../../src/services/pages/OverlayPages.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAiB,EAAY,KAAK,gBAAgB,EAAC,MAAM,YAAY,CAAA;AAIrE,MAAM,MAAM,yBAAyB,GAAG,OAAO,GAAG,QAAQ,GAAG,UAAU,GAAG,YAAY,CAAA;AAEtF,MAAM,WAAW,gBAAgB;IAC/B,+CAA+C;IAC/C,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;CAChE;AAED,cAAM,YAAY;IAChB,OAAO,CAAC,IAAI,CAAgC;IAC5C,OAAO,CAAC,KAAK,CAA0B;IACvC,gFAAgF;IAChF,OAAO,CAAC,MAAM,CAAoB;IAElC,OAAO,CAAC,IAAI,EAAE,gBAAgB,GAAG,IAAI,GAAG,IAAI;IAI5C,kDAAkD;IAClD,IAAI,cAAc,IAAI,MAAM,GAAG,IAAI,CAKlC;IAED,uFAAuF;IACvF,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;IAOlC,yFAAyF;IACzF,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,GAAE,gBAA2B,GAAG,IAAI;IAKpE,iFAAiF;IACjF,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;IAYjC,OAAO,CAAC,OAAO;IAYf,OAAO,CAAC,IAAI;CAIb;AAED,QAAA,MAAM,YAAY,cAAqB,CAAA;AACvC,eAAe,YAAY,CAAA;AAC3B,OAAO,EAAC,YAAY,EAAC,CAAA"}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The overlay
|
|
3
|
-
* slot, the only Page implementation the page host shows. The
|
|
4
|
-
* the slot and the eligibility watch; a page only relays:
|
|
5
|
-
* is reported to its app as a view change, and the app's
|
|
6
|
-
* slot through `renderOverlay` under its package name.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
2
|
+
* The overlay app's page (ADR-0006, ADR-0009): one Page per dashboard that
|
|
3
|
+
* has taken the slot, the only Page implementation the page host shows. The
|
|
4
|
+
* engine keeps the slot and the eligibility watch; a page only relays:
|
|
5
|
+
* holding the slot is reported to its app as a view change, and the app's
|
|
6
|
+
* frames reach the slot through `renderOverlay` under its package name. Only
|
|
7
|
+
* the dashboard that holds the role may show; when the role moves, the
|
|
8
|
+
* previous holder hears it was replaced. The runtime supplies the sink that
|
|
9
|
+
* delivers to apps, so this module never imports it.
|
|
9
10
|
*/
|
|
10
11
|
import pageHost from "./PageHost";
|
|
11
12
|
import { isOverlayApp } from "./overlayApps";
|
|
@@ -36,11 +37,11 @@ class OverlayPages {
|
|
|
36
37
|
pageHost.show(page);
|
|
37
38
|
return pageHost.showing === page;
|
|
38
39
|
}
|
|
39
|
-
/** Release the slot if the app holds it. */
|
|
40
|
-
hide(packageName) {
|
|
40
|
+
/** Release the slot if the app holds it; "replaced" when the dashboard role moved on. */
|
|
41
|
+
hide(packageName, reason = "hidden") {
|
|
41
42
|
const page = this.pages.get(packageName);
|
|
42
43
|
if (page)
|
|
43
|
-
pageHost.hide(page);
|
|
44
|
+
pageHost.hide(page, reason);
|
|
44
45
|
}
|
|
45
46
|
/** The app stopped: release the slot without telling it, and forget its page. */
|
|
46
47
|
remove(packageName) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OverlayPages.js","sourceRoot":"","sources":["../../../src/services/pages/OverlayPages.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"OverlayPages.js","sourceRoot":"","sources":["../../../src/services/pages/OverlayPages.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The overlay app's page (ADR-0006, ADR-0009): one Page per dashboard that\n * has taken the slot, the only Page implementation the page host shows. The\n * engine keeps the slot and the eligibility watch; a page only relays:\n * holding the slot is reported to its app as a view change, and the app's\n * frames reach the slot through `renderOverlay` under its package name. Only\n * the dashboard that holds the role may show; when the role moves, the\n * previous holder hears it was replaced. The runtime supplies the sink that\n * delivers to apps, so this module never imports it.\n */\nimport pageHost, {type Page, type PageHiddenReason} from \"./PageHost\"\nimport {isOverlayApp} from \"./overlayApps\"\nimport {pagesEligible} from \"./pageEligibility\"\n\nexport type DashboardViewChangeReason = \"shown\" | \"hidden\" | \"replaced\" | \"ineligible\"\n\nexport interface OverlayPagesSink {\n /** Deliver a display_view event to the app. */\n send(packageName: string, event: Record<string, unknown>): void\n}\n\nclass OverlayPages {\n private sink: OverlayPagesSink | null = null\n private pages = new Map<string, Page>()\n /** Set while an app's page is torn down with the app; it hears nothing then. */\n private silent = new Set<string>()\n\n setSink(sink: OverlayPagesSink | null): void {\n this.sink = sink\n }\n\n /** The app whose page holds the slot, or null. */\n get showingPackage(): string | null {\n const showing = pageHost.showing\n if (!showing) return null\n for (const [packageName, page] of this.pages) if (page === showing) return packageName\n return null\n }\n\n /** Give the app the slot (a no-op when it already shows or pages are not eligible). */\n show(packageName: string): boolean {\n if (!isOverlayApp(packageName) || !pagesEligible()) return false\n const page = this.pageFor(packageName)\n pageHost.show(page)\n return pageHost.showing === page\n }\n\n /** Release the slot if the app holds it; \"replaced\" when the dashboard role moved on. */\n hide(packageName: string, reason: PageHiddenReason = \"hidden\"): void {\n const page = this.pages.get(packageName)\n if (page) pageHost.hide(page, reason)\n }\n\n /** The app stopped: release the slot without telling it, and forget its page. */\n remove(packageName: string): void {\n const page = this.pages.get(packageName)\n if (!page) return\n this.silent.add(packageName)\n try {\n pageHost.hide(page)\n } finally {\n this.silent.delete(packageName)\n }\n this.pages.delete(packageName)\n }\n\n private pageFor(packageName: string): Page {\n let page = this.pages.get(packageName)\n if (page) return page\n page = {\n id: packageName,\n onShown: () => this.emit(packageName, {kind: \"change\", view: \"dashboard\", reason: \"shown\"}),\n onHidden: (reason: PageHiddenReason) => this.emit(packageName, {kind: \"change\", view: \"main\", reason}),\n }\n this.pages.set(packageName, page)\n return page\n }\n\n private emit(packageName: string, event: Record<string, unknown>): void {\n if (this.silent.has(packageName) || !isOverlayApp(packageName)) return\n this.sink?.send(packageName, event)\n }\n}\n\nconst overlayPages = new OverlayPages()\nexport default overlayPages\nexport {OverlayPages}\n"]}
|
|
@@ -20,7 +20,7 @@ declare class PageHost {
|
|
|
20
20
|
/** Give `page` the slot; no-op when it already shows or pages are not eligible. */
|
|
21
21
|
show(page: Page): void;
|
|
22
22
|
/** Release the slot if `page` holds it. */
|
|
23
|
-
hide(page: Page): void;
|
|
23
|
+
hide(page: Page, reason?: PageHiddenReason): void;
|
|
24
24
|
onChange(listener: (page: Page | null) => void): () => void;
|
|
25
25
|
private release;
|
|
26
26
|
private emit;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PageHost.d.ts","sourceRoot":"","sources":["../../../src/services/pages/PageHost.ts"],"names":[],"mappings":"AAeA,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,UAAU,GAAG,YAAY,CAAA;AAEnE,MAAM,WAAW,IAAI;IACnB,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,qDAAqD;IACrD,OAAO,IAAI,IAAI,CAAA;IACf,8EAA8E;IAC9E,QAAQ,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAAA;CACzC;AAED,cAAM,QAAQ;IACZ,OAAO,CAAC,WAAW,CAAoB;IACvC,OAAO,CAAC,GAAG,CAAwB;IACnC,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,SAAS,CAAyC;IAC1D,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,OAAO,CAAQ;IAEvB,KAAK,IAAI,IAAI;IAeb,IAAI,IAAI,IAAI;IAOZ,IAAI,OAAO,IAAI,IAAI,GAAG,IAAI,CAEzB;IAED,mFAAmF;IACnF,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;IAYtB,2CAA2C;IAC3C,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;
|
|
1
|
+
{"version":3,"file":"PageHost.d.ts","sourceRoot":"","sources":["../../../src/services/pages/PageHost.ts"],"names":[],"mappings":"AAeA,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,UAAU,GAAG,YAAY,CAAA;AAEnE,MAAM,WAAW,IAAI;IACnB,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,qDAAqD;IACrD,OAAO,IAAI,IAAI,CAAA;IACf,8EAA8E;IAC9E,QAAQ,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAAA;CACzC;AAED,cAAM,QAAQ;IACZ,OAAO,CAAC,WAAW,CAAoB;IACvC,OAAO,CAAC,GAAG,CAAwB;IACnC,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,SAAS,CAAyC;IAC1D,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,OAAO,CAAQ;IAEvB,KAAK,IAAI,IAAI;IAeb,IAAI,IAAI,IAAI;IAOZ,IAAI,OAAO,IAAI,IAAI,GAAG,IAAI,CAEzB;IAED,mFAAmF;IACnF,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;IAYtB,2CAA2C;IAC3C,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,GAAE,gBAA2B,GAAG,IAAI;IAO3D,QAAQ,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,KAAK,IAAI,GAAG,MAAM,IAAI;IAO3D,OAAO,CAAC,OAAO;IASf,OAAO,CAAC,IAAI;IAIZ,OAAO,CAAC,OAAO;CAUhB;AAED,QAAA,MAAM,QAAQ,UAAiB,CAAA;AAC/B,eAAe,QAAQ,CAAA;AACvB,OAAO,EAAC,QAAQ,EAAC,CAAA"}
|
|
@@ -53,11 +53,11 @@ class PageHost {
|
|
|
53
53
|
});
|
|
54
54
|
}
|
|
55
55
|
/** Release the slot if `page` holds it. */
|
|
56
|
-
hide(page) {
|
|
56
|
+
hide(page, reason = "hidden") {
|
|
57
57
|
this.enqueue(() => {
|
|
58
58
|
if (this.showingPage !== page)
|
|
59
59
|
return;
|
|
60
|
-
this.release(
|
|
60
|
+
this.release(reason);
|
|
61
61
|
});
|
|
62
62
|
}
|
|
63
63
|
onChange(listener) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PageHost.js","sourceRoot":"","sources":["../../../src/services/pages/PageHost.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAC,eAAe,EAAC,MAAM,sBAAsB,CAAA;AACpD,OAAO,EAAC,QAAQ,EAAE,gBAAgB,EAAC,MAAM,uBAAuB,CAAA;AAChE,OAAO,mBAAmB,MAAM,wBAAwB,CAAA;AACxD,OAAO,EAAC,aAAa,EAAC,MAAM,mBAAmB,CAAA;AAa/C,MAAM,QAAQ;IACJ,WAAW,GAAgB,IAAI,CAAA;IAC/B,GAAG,GAAsB,EAAE,CAAA;IAC3B,QAAQ,GAAG,KAAK,CAAA;IAChB,SAAS,GAAG,IAAI,GAAG,EAA+B,CAAA;IAClD,MAAM,GAAsB,EAAE,CAAA;IAC9B,OAAO,GAAG,KAAK,CAAA;IAEvB,KAAK;QACH,IAAI,IAAI,CAAC,OAAO;YAAE,OAAM;QACxB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAA;QACnB,MAAM,KAAK,GAAG,GAAG,EAAE;YACjB,IAAI,IAAI,CAAC,WAAW,IAAI,CAAC,aAAa,EAAE;gBAAE,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAA;QACtE,CAAC,CAAA;QACD,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,eAAe,CAAC,SAAS,CACvB,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,KAAK,CAAC,UAAU,CAAC,KAAK,IAAI,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,eAAe,EAAE,EACpF,KAAK,CACN,EACD,gBAAgB,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,wBAAwB,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,CACtG,CAAA;IACH,CAAC;IAED,IAAI;QACF,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM;YAAE,KAAK,EAAE,CAAA;QACxC,IAAI,CAAC,MAAM,GAAG,EAAE,CAAA;QAChB,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;QACtB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAA;IACtB,CAAC;IAED,IAAI,OAAO;QACT,OAAO,IAAI,CAAC,WAAW,CAAA;IACzB,CAAC;IAED,mFAAmF;IACnF,IAAI,CAAC,IAAU;QACb,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE;YAChB,IAAI,IAAI,CAAC,WAAW,KAAK,IAAI,IAAI,CAAC,aAAa,EAAE;gBAAE,OAAM;YACzD,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,CAAA;YACjC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAA;YACvB,mBAAmB,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;YAC5C,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAA;YAC9B,IAAI,CAAC,OAAO,EAAE,CAAA;YACd,IAAI,CAAC,IAAI,EAAE,CAAA;QACb,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,2CAA2C;IAC3C,IAAI,CAAC,IAAU;
|
|
1
|
+
{"version":3,"file":"PageHost.js","sourceRoot":"","sources":["../../../src/services/pages/PageHost.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The page host owns the overlay slot: the single native view above the main\n * content. At most one page shows at a time; showing a page replaces whatever\n * was showing, hiding releases the slot and the main content replays. Which\n * app gets the wearer's gestures is the gesture claims' business, not the\n * host's. Show and hide run through one queue so a page\n * that hides itself from inside its own transition is hidden afterwards, never\n * re-entered. Eligibility (Glassly CFW, dashboard enabled) is checked before a\n * show and watched while a page is up.\n */\nimport {useGlassesStore} from \"../../stores/glasses\"\nimport {SETTINGS, useSettingsStore} from \"../../stores/settings\"\nimport localDisplayManager from \"../LocalDisplayManager\"\nimport {pagesEligible} from \"./pageEligibility\"\n\nexport type PageHiddenReason = \"hidden\" | \"replaced\" | \"ineligible\"\n\nexport interface Page {\n /** Scene app id; the overlay owner while the page shows. */\n readonly id: string\n /** The page holds the slot: draw the first frame. */\n onShown(): void\n /** The slot was released, taken by another page, or lost with eligibility. */\n onHidden(reason: PageHiddenReason): void\n}\n\nclass PageHost {\n private showingPage: Page | null = null\n private ops: Array<() => void> = []\n private draining = false\n private listeners = new Set<(page: Page | null) => void>()\n private unsubs: Array<() => void> = []\n private started = false\n\n start(): void {\n if (this.started) return\n this.started = true\n const check = () => {\n if (this.showingPage && !pagesEligible()) this.release(\"ineligible\")\n }\n this.unsubs.push(\n useGlassesStore.subscribe(\n (state) => `${state.connection.state}:${state.deviceModel}:${state.firmwareVersion}`,\n check,\n ),\n useSettingsStore.subscribe((state) => state.getSetting(SETTINGS.custom_dashboard_enabled.key), check),\n )\n }\n\n stop(): void {\n for (const unsub of this.unsubs) unsub()\n this.unsubs = []\n this.release(\"hidden\")\n this.started = false\n }\n\n get showing(): Page | null {\n return this.showingPage\n }\n\n /** Give `page` the slot; no-op when it already shows or pages are not eligible. */\n show(page: Page): void {\n this.enqueue(() => {\n if (this.showingPage === page || !pagesEligible()) return\n const previous = this.showingPage\n this.showingPage = page\n localDisplayManager.setOverlayOwner(page.id)\n previous?.onHidden(\"replaced\")\n page.onShown()\n this.emit()\n })\n }\n\n /** Release the slot if `page` holds it. */\n hide(page: Page, reason: PageHiddenReason = \"hidden\"): void {\n this.enqueue(() => {\n if (this.showingPage !== page) return\n this.release(reason)\n })\n }\n\n onChange(listener: (page: Page | null) => void): () => void {\n this.listeners.add(listener)\n return () => {\n this.listeners.delete(listener)\n }\n }\n\n private release(reason: PageHiddenReason): void {\n const page = this.showingPage\n if (!page) return\n this.showingPage = null\n localDisplayManager.setOverlayOwner(null)\n page.onHidden(reason)\n this.emit()\n }\n\n private emit(): void {\n for (const listener of Array.from(this.listeners)) listener(this.showingPage)\n }\n\n private enqueue(op: () => void): void {\n this.ops.push(op)\n if (this.draining) return\n this.draining = true\n try {\n while (this.ops.length) (this.ops.shift() as () => void)()\n } finally {\n this.draining = false\n }\n }\n}\n\nconst pageHost = new PageHost()\nexport default pageHost\nexport {PageHost}\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function resolveActiveDashboard(installed: readonly string[], chosen: unknown, fallback: string): string | null;
|
|
2
|
+
/** The active dashboard among `apps`; only miniapps whose manifest type is "dashboard" can hold the role. */
|
|
3
|
+
export declare function activeDashboardAmong(apps: ReadonlyArray<{
|
|
4
|
+
packageName: string;
|
|
5
|
+
type?: string;
|
|
6
|
+
}>): string | null;
|
|
7
|
+
//# sourceMappingURL=activeDashboard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"activeDashboard.d.ts","sourceRoot":"","sources":["../../../src/services/pages/activeDashboard.ts"],"names":[],"mappings":"AASA,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAIrH;AAED,6GAA6G;AAC7G,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,aAAa,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAC,CAAC,GAAG,MAAM,GAAG,IAAI,CAO7G"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Who holds the dashboard role (ADR-0009): the wearer's choice when that
|
|
3
|
+
* miniapp is installed, else the default (the bundled dashboard), else the
|
|
4
|
+
* first installed dashboard by package name, else nobody. Read at decision
|
|
5
|
+
* time, never cached, so an uninstall hands the role back without anyone
|
|
6
|
+
* rewriting the setting.
|
|
7
|
+
*/
|
|
8
|
+
import { SETTINGS, useSettingsStore } from "../../stores/settings";
|
|
9
|
+
export function resolveActiveDashboard(installed, chosen, fallback) {
|
|
10
|
+
if (typeof chosen === "string" && installed.includes(chosen))
|
|
11
|
+
return chosen;
|
|
12
|
+
if (installed.includes(fallback))
|
|
13
|
+
return fallback;
|
|
14
|
+
return [...installed].sort()[0] ?? null;
|
|
15
|
+
}
|
|
16
|
+
/** The active dashboard among `apps`; only miniapps whose manifest type is "dashboard" can hold the role. */
|
|
17
|
+
export function activeDashboardAmong(apps) {
|
|
18
|
+
const installed = apps.filter((app) => app.type === "dashboard").map((app) => app.packageName);
|
|
19
|
+
return resolveActiveDashboard(installed, useSettingsStore.getState().getSetting(SETTINGS.active_dashboard.key), SETTINGS.active_dashboard.defaultValue());
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=activeDashboard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"activeDashboard.js","sourceRoot":"","sources":["../../../src/services/pages/activeDashboard.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAC,QAAQ,EAAE,gBAAgB,EAAC,MAAM,uBAAuB,CAAA;AAEhE,MAAM,UAAU,sBAAsB,CAAC,SAA4B,EAAE,MAAe,EAAE,QAAgB;IACpG,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,CAAA;IAC3E,IAAI,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAAE,OAAO,QAAQ,CAAA;IACjD,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAA;AACzC,CAAC;AAED,6GAA6G;AAC7G,MAAM,UAAU,oBAAoB,CAAC,IAAyD;IAC5F,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;IAC9F,OAAO,sBAAsB,CAC3B,SAAS,EACT,gBAAgB,CAAC,QAAQ,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,gBAAgB,CAAC,GAAG,CAAC,EACrE,QAAQ,CAAC,gBAAgB,CAAC,YAAY,EAAE,CACzC,CAAA;AACH,CAAC","sourcesContent":["/**\n * Who holds the dashboard role (ADR-0009): the wearer's choice when that\n * miniapp is installed, else the default (the bundled dashboard), else the\n * first installed dashboard by package name, else nobody. Read at decision\n * time, never cached, so an uninstall hands the role back without anyone\n * rewriting the setting.\n */\nimport {SETTINGS, useSettingsStore} from \"../../stores/settings\"\n\nexport function resolveActiveDashboard(installed: readonly string[], chosen: unknown, fallback: string): string | null {\n if (typeof chosen === \"string\" && installed.includes(chosen)) return chosen\n if (installed.includes(fallback)) return fallback\n return [...installed].sort()[0] ?? null\n}\n\n/** The active dashboard among `apps`; only miniapps whose manifest type is \"dashboard\" can hold the role. */\nexport function activeDashboardAmong(apps: ReadonlyArray<{packageName: string; type?: string}>): string | null {\n const installed = apps.filter((app) => app.type === \"dashboard\").map((app) => app.packageName)\n return resolveActiveDashboard(\n installed,\n useSettingsStore.getState().getSetting(SETTINGS.active_dashboard.key),\n SETTINGS.active_dashboard.defaultValue(),\n )\n}\n"]}
|
|
@@ -1,23 +1,30 @@
|
|
|
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
|
export type DisplayView = "main" | "dashboard";
|
|
12
|
+
/** `packageName` holds the dashboard role and is connected. */
|
|
11
13
|
export declare function isOverlayApp(packageName: string): boolean;
|
|
14
|
+
/** `packageName` is a connected dashboard miniapp, whether or not it holds the role. */
|
|
15
|
+
export declare function isDashboardApp(packageName: string): boolean;
|
|
12
16
|
export declare function overlayApps(): readonly string[];
|
|
13
|
-
|
|
14
|
-
export declare function
|
|
15
|
-
|
|
16
|
-
export declare function
|
|
17
|
-
|
|
17
|
+
/** The package that holds the dashboard role, connected or not. */
|
|
18
|
+
export declare function activeDashboard(): string | null;
|
|
19
|
+
export declare function setActiveDashboard(packageName: string | null): void;
|
|
20
|
+
export declare function addDashboardApp(packageName: string): void;
|
|
21
|
+
export declare function removeDashboardApp(packageName: string): void;
|
|
22
|
+
/** Record whether a dashboard subscribes to phone notifications (the overlay app is the viewer while it does). */
|
|
23
|
+
export declare function setOverlayViewer(packageName: string, subscribed: boolean): void;
|
|
24
|
+
/** The overlay app presents phone notifications. */
|
|
18
25
|
export declare function overlayViewerRunning(): boolean;
|
|
19
26
|
export declare function onOverlayAppsChange(listener: () => void): () => void;
|
|
20
|
-
/** The views `packageName` may draw: main for everyone, the dashboard for overlay
|
|
27
|
+
/** The views `packageName` may draw: main for everyone, the dashboard for the overlay app on eligible glasses. */
|
|
21
28
|
export declare function displayViewsFor(packageName: string, eligible: boolean): DisplayView[];
|
|
22
29
|
/** Tests only. */
|
|
23
30
|
export declare function _resetOverlayAppsForTest(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"overlayApps.d.ts","sourceRoot":"","sources":["../../../src/services/pages/overlayApps.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"overlayApps.d.ts","sourceRoot":"","sources":["../../../src/services/pages/overlayApps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,WAAW,CAAA;AAS9C,+DAA+D;AAC/D,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED,wFAAwF;AACxF,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAE3D;AAED,wBAAgB,WAAW,IAAI,SAAS,MAAM,EAAE,CAE/C;AAED,mEAAmE;AACnE,wBAAgB,eAAe,IAAI,MAAM,GAAG,IAAI,CAE/C;AAED,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAInE;AAED,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAIzD;AAED,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAI5D;AAED,kHAAkH;AAClH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,GAAG,IAAI,CAK/E;AAED,oDAAoD;AACpD,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAKpE;AAED,kHAAkH;AAClH,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,WAAW,EAAE,CAErF;AAMD,kBAAkB;AAClB,wBAAgB,wBAAwB,IAAI,IAAI,CAI/C"}
|
|
@@ -1,47 +1,65 @@
|
|
|
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
|
-
|
|
12
|
-
|
|
11
|
+
/** Connected dashboard miniapps, role or not. */
|
|
12
|
+
const connected = new Set();
|
|
13
|
+
/** Connected dashboards listening to the phone's notifications. */
|
|
14
|
+
const listening = new Set();
|
|
15
|
+
let active = null;
|
|
13
16
|
const listeners = new Set();
|
|
17
|
+
/** `packageName` holds the dashboard role and is connected. */
|
|
14
18
|
export function isOverlayApp(packageName) {
|
|
15
|
-
return
|
|
19
|
+
return packageName === active && connected.has(packageName);
|
|
20
|
+
}
|
|
21
|
+
/** `packageName` is a connected dashboard miniapp, whether or not it holds the role. */
|
|
22
|
+
export function isDashboardApp(packageName) {
|
|
23
|
+
return connected.has(packageName);
|
|
16
24
|
}
|
|
17
25
|
export function overlayApps() {
|
|
18
|
-
return [
|
|
26
|
+
return active !== null && connected.has(active) ? [active] : [];
|
|
27
|
+
}
|
|
28
|
+
/** The package that holds the dashboard role, connected or not. */
|
|
29
|
+
export function activeDashboard() {
|
|
30
|
+
return active;
|
|
31
|
+
}
|
|
32
|
+
export function setActiveDashboard(packageName) {
|
|
33
|
+
if (active === packageName)
|
|
34
|
+
return;
|
|
35
|
+
active = packageName;
|
|
36
|
+
emit();
|
|
19
37
|
}
|
|
20
|
-
export function
|
|
21
|
-
if (
|
|
38
|
+
export function addDashboardApp(packageName) {
|
|
39
|
+
if (connected.has(packageName))
|
|
22
40
|
return;
|
|
23
|
-
|
|
41
|
+
connected.add(packageName);
|
|
24
42
|
emit();
|
|
25
43
|
}
|
|
26
|
-
export function
|
|
27
|
-
|
|
28
|
-
if (!
|
|
44
|
+
export function removeDashboardApp(packageName) {
|
|
45
|
+
listening.delete(packageName);
|
|
46
|
+
if (!connected.delete(packageName))
|
|
29
47
|
return;
|
|
30
48
|
emit();
|
|
31
49
|
}
|
|
32
|
-
/** Record whether
|
|
33
|
-
export function setOverlayViewer(packageName,
|
|
34
|
-
const was =
|
|
35
|
-
if (
|
|
36
|
-
|
|
50
|
+
/** Record whether a dashboard subscribes to phone notifications (the overlay app is the viewer while it does). */
|
|
51
|
+
export function setOverlayViewer(packageName, subscribed) {
|
|
52
|
+
const was = overlayViewerRunning();
|
|
53
|
+
if (subscribed && connected.has(packageName))
|
|
54
|
+
listening.add(packageName);
|
|
37
55
|
else
|
|
38
|
-
|
|
39
|
-
if (was !==
|
|
56
|
+
listening.delete(packageName);
|
|
57
|
+
if (was !== overlayViewerRunning())
|
|
40
58
|
emit();
|
|
41
59
|
}
|
|
42
|
-
/**
|
|
60
|
+
/** The overlay app presents phone notifications. */
|
|
43
61
|
export function overlayViewerRunning() {
|
|
44
|
-
return
|
|
62
|
+
return active !== null && connected.has(active) && listening.has(active);
|
|
45
63
|
}
|
|
46
64
|
export function onOverlayAppsChange(listener) {
|
|
47
65
|
listeners.add(listener);
|
|
@@ -49,9 +67,9 @@ export function onOverlayAppsChange(listener) {
|
|
|
49
67
|
listeners.delete(listener);
|
|
50
68
|
};
|
|
51
69
|
}
|
|
52
|
-
/** The views `packageName` may draw: main for everyone, the dashboard for overlay
|
|
70
|
+
/** The views `packageName` may draw: main for everyone, the dashboard for the overlay app on eligible glasses. */
|
|
53
71
|
export function displayViewsFor(packageName, eligible) {
|
|
54
|
-
return
|
|
72
|
+
return isOverlayApp(packageName) && eligible ? ["main", "dashboard"] : ["main"];
|
|
55
73
|
}
|
|
56
74
|
function emit() {
|
|
57
75
|
for (const listener of Array.from(listeners))
|
|
@@ -59,7 +77,8 @@ function emit() {
|
|
|
59
77
|
}
|
|
60
78
|
/** Tests only. */
|
|
61
79
|
export function _resetOverlayAppsForTest() {
|
|
62
|
-
|
|
63
|
-
|
|
80
|
+
connected.clear();
|
|
81
|
+
listening.clear();
|
|
82
|
+
active = null;
|
|
64
83
|
}
|
|
65
84
|
//# sourceMappingURL=overlayApps.js.map
|