@glassly/engine 0.3.7-dev.964 → 0.3.7-dev.969

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.
@@ -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;AACxF,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,+EAA+E;IAC/E,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,mBAAmB,GAAG,IAAI;IAI1E,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;IAiH7F;;;;;;;;;;OAUG;IACG,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;IAwBpF,iFAAiF;YACnE,KAAK;IAkCnB;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACG,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC;IAsC7C;;;;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"}
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;AACxF,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,+EAA+E;IAC/E,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,mBAAmB,GAAG,IAAI;IAI1E,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;IAiH7F;;;;;;;;;;OAUG;IACG,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;IA0BpF,iFAAiF;YACnE,KAAK;IAkCnB;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACG,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC;IAsC7C;;;;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"}
@@ -175,6 +175,8 @@ class MiniappLauncher {
175
175
  */
176
176
  async ensureRunning(packageName, hints) {
177
177
  const router = this.requireRouter();
178
+ // A context still tearing down from a stop is not one to reuse.
179
+ await router.whenUnregistered(packageName);
178
180
  // Already spawned: best-effort resolve for the UI entry, never throw —
179
181
  // a headless caller that doesn't need UI gets {null, null} fast even if
180
182
  // the dev server has since dropped.
@@ -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 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 /** Tell an already-running miniapp the user opened it again and from where. */\n notifyLaunch(packageName: string, launchSource: MiniappLaunchSource): void {\n this.deps?.router.notifyLaunch(packageName, launchSource)\n }\n\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 widgets: manifest.widgets,\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 widgets?: 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 widgets: manifest.widgets,\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 * Start the Always On local miniapps at boot.\n *\n * Miniapps run in the phone's own JS engine, so a host-process kill tears\n * down their JSContext. Only Always On apps come back: whatever else was\n * running when the app was killed stays stopped until the wearer opens it\n * again. The spawn is headless (no foreground, no navigation) and registers\n * the package so the home tray/switcher project it as running, exactly as a\n * normal start would. The WebView, if any, re-attaches lazily when the user\n * opens the app.\n *\n * One dashboard runs at a time: a dashboard already running keeps its place,\n * else the first Always On dashboard by package name starts and the rest\n * stay stopped. The flag is exclusive after any user action (setAlwaysOn\n * clears it on the other dashboards), so this only arbitrates manifests\n * that both claim it.\n *\n * Idempotent and best-effort: skips already-spawned contexts, and skips apps\n * missing required OS permissions without prompting or clearing their Always\n * On choice. Not compatibility-gated: an Always On app shouldn't be dropped\n * just because glasses are momentarily disconnected at boot — it resumes\n * when they reconnect, same as mid-session.\n */\n async autostartLocalMiniapps(): Promise<void> {\n const apps = await appRegistry.getInstalledMiniapps()\n const dashboards = apps.filter((app) => app.type === \"dashboard\")\n const dashboard =\n dashboards.find((app) => this.isRunning(app.packageName))?.packageName ??\n dashboards\n .filter((app) => app.alwaysOn)\n .map((app) => app.packageName)\n .sort()[0] ??\n null\n for (const app of apps) {\n // Only phone-hosted miniapps have a JSContext to spawn. Offline\n // built-ins (`local:false, offline:true`) have no bundle to launch.\n if (!app.local || !app.alwaysOn) continue\n if (app.type === \"dashboard\" && app.packageName !== dashboard) 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}`, e)\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 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 /** Tell an already-running miniapp the user opened it again and from where. */\n notifyLaunch(packageName: string, launchSource: MiniappLaunchSource): void {\n this.deps?.router.notifyLaunch(packageName, launchSource)\n }\n\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 widgets: manifest.widgets,\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 widgets?: 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 widgets: manifest.widgets,\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 // A context still tearing down from a stop is not one to reuse.\n await router.whenUnregistered(packageName)\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 * Start the Always On local miniapps at boot.\n *\n * Miniapps run in the phone's own JS engine, so a host-process kill tears\n * down their JSContext. Only Always On apps come back: whatever else was\n * running when the app was killed stays stopped until the wearer opens it\n * again. The spawn is headless (no foreground, no navigation) and registers\n * the package so the home tray/switcher project it as running, exactly as a\n * normal start would. The WebView, if any, re-attaches lazily when the user\n * opens the app.\n *\n * One dashboard runs at a time: a dashboard already running keeps its place,\n * else the first Always On dashboard by package name starts and the rest\n * stay stopped. The flag is exclusive after any user action (setAlwaysOn\n * clears it on the other dashboards), so this only arbitrates manifests\n * that both claim it.\n *\n * Idempotent and best-effort: skips already-spawned contexts, and skips apps\n * missing required OS permissions without prompting or clearing their Always\n * On choice. Not compatibility-gated: an Always On app shouldn't be dropped\n * just because glasses are momentarily disconnected at boot — it resumes\n * when they reconnect, same as mid-session.\n */\n async autostartLocalMiniapps(): Promise<void> {\n const apps = await appRegistry.getInstalledMiniapps()\n const dashboards = apps.filter((app) => app.type === \"dashboard\")\n const dashboard =\n dashboards.find((app) => this.isRunning(app.packageName))?.packageName ??\n dashboards\n .filter((app) => app.alwaysOn)\n .map((app) => app.packageName)\n .sort()[0] ??\n null\n for (const app of apps) {\n // Only phone-hosted miniapps have a JSContext to spawn. Offline\n // built-ins (`local:false, offline:true`) have no bundle to launch.\n if (!app.local || !app.alwaysOn) continue\n if (app.type === \"dashboard\" && app.packageName !== dashboard) 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}`, e)\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"]}
@@ -26,7 +26,7 @@ export function isCustomDashboardEligible(info) {
26
26
  if (info.model === DeviceTypes.MEMOMIND_ONE) {
27
27
  const match = /^GLASSLYCFW\/([0-9]+)$/.exec(info.firmwareVersion ?? "");
28
28
  const revision = match ? Number(match[1]) : 0;
29
- return Number.isSafeInteger(revision) && revision >= 5;
29
+ return Number.isSafeInteger(revision) && revision >= 7;
30
30
  }
31
31
  return (getModelCapabilities(info.model).hasCustomFirmware === true &&
32
32
  isCfwFirmwareVersion(info.firmwareVersion));
@@ -1 +1 @@
1
- {"version":3,"file":"pageEligibility.js","sourceRoot":"","sources":["../../../src/services/pages/pageEligibility.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAC,eAAe,EAAC,MAAM,sBAAsB,CAAA;AACpD,OAAO,EAAC,QAAQ,EAAE,gBAAgB,EAAC,MAAM,uBAAuB,CAAA;AAChE,OAAO,EAAC,WAAW,EAAC,MAAM,mBAAmB,CAAA;AAC7C,OAAO,EAAC,oBAAoB,EAAC,MAAM,sBAAsB,CAAA;AACzD,OAAO,EAAC,kBAAkB,EAAC,MAAM,qBAAqB,CAAA;AAEtD,0GAA0G;AAC1G,MAAM,UAAU,oBAAoB,CAAC,eAA0C;IAC7E,OAAO,CACL,eAAe,EAAE,WAAW,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,KAAK,IAAI;QAC/D,sBAAsB,CAAC,IAAI,CAAC,eAAe,IAAI,EAAE,CAAC,CACnD,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,yBAAyB,CAAC,IAA8D;IACtG,IAAI,CAAC,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAA;IAC7B,IAAI,IAAI,CAAC,KAAK,KAAK,WAAW,CAAC,YAAY,EAAE,CAAC;QAC5C,MAAM,KAAK,GAAG,wBAAwB,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,IAAI,EAAE,CAAC,CAAA;QACvE,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;QAC7C,OAAO,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,QAAQ,IAAI,CAAC,CAAA;IACxD,CAAC;IACD,OAAO,CACL,oBAAoB,CAAC,IAAI,CAAC,KAAoB,CAAC,CAAC,iBAAiB,KAAK,IAAI;QAC1E,oBAAoB,CAAC,IAAI,CAAC,eAAe,CAAC,CAC3C,CAAA;AACH,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,IAAI,gBAAgB,CAAC,QAAQ,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,wBAAwB,CAAC,GAAG,CAAC,KAAK,KAAK;QAAE,OAAO,KAAK,CAAA;IACzG,MAAM,KAAK,GAAG,eAAe,CAAC,QAAQ,EAAE,CAAA;IACxC,OAAO,CACL,kBAAkB,CAAC,KAAK,CAAC,UAAU,CAAC;QACpC,yBAAyB,CAAC,EAAC,KAAK,EAAE,KAAK,CAAC,WAAW,EAAE,eAAe,EAAE,KAAK,CAAC,eAAe,EAAC,CAAC,CAC9F,CAAA;AACH,CAAC","sourcesContent":["/**\n * Whether the dashboard view may show right now: connected Glassly CFW\n * glasses with the dashboard enabled. The page host reads it before showing\n * a page and hides the showing page when it turns false; the miniapp runtime\n * folds it into the designated app's `display.views` capability. Read at\n * decision time, never cached.\n */\nimport {useGlassesStore} from \"../../stores/glasses\"\nimport {SETTINGS, useSettingsStore} from \"../../stores/settings\"\nimport {DeviceTypes} from \"../../types/enums\"\nimport {getModelCapabilities} from \"../../types/hardware\"\nimport {isGlassesConnected} from \"../GlassesReadiness\"\n\n/** The SGC appends this marker after the firmware advertises Glassly CFW capabilities (G2, RayNeo iO). */\nexport function isCfwFirmwareVersion(firmwareVersion: string | null | undefined): boolean {\n return (\n firmwareVersion?.toLowerCase().includes(\"glassly cfw\") === true ||\n /^GLASSLYCFW\\/[0-9]+$/.test(firmwareVersion ?? \"\")\n )\n}\n\n/**\n * Only models that ship a Glassly custom firmware, and only while they run it.\n * Stock firmware on those models and every other model (NIMO, G1, ...) are\n * intentionally excluded.\n */\nexport function isCustomDashboardEligible(info: {model?: string | null; firmwareVersion?: string | null}): boolean {\n if (!info.model) return false\n if (info.model === DeviceTypes.MEMOMIND_ONE) {\n const match = /^GLASSLYCFW\\/([0-9]+)$/.exec(info.firmwareVersion ?? \"\")\n const revision = match ? Number(match[1]) : 0\n return Number.isSafeInteger(revision) && revision >= 5\n }\n return (\n getModelCapabilities(info.model as DeviceTypes).hasCustomFirmware === true &&\n isCfwFirmwareVersion(info.firmwareVersion)\n )\n}\n\nexport function pagesEligible(): boolean {\n if (useSettingsStore.getState().getSetting(SETTINGS.custom_dashboard_enabled.key) === false) return false\n const state = useGlassesStore.getState()\n return (\n isGlassesConnected(state.connection) &&\n isCustomDashboardEligible({model: state.deviceModel, firmwareVersion: state.firmwareVersion})\n )\n}\n"]}
1
+ {"version":3,"file":"pageEligibility.js","sourceRoot":"","sources":["../../../src/services/pages/pageEligibility.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAC,eAAe,EAAC,MAAM,sBAAsB,CAAA;AACpD,OAAO,EAAC,QAAQ,EAAE,gBAAgB,EAAC,MAAM,uBAAuB,CAAA;AAChE,OAAO,EAAC,WAAW,EAAC,MAAM,mBAAmB,CAAA;AAC7C,OAAO,EAAC,oBAAoB,EAAC,MAAM,sBAAsB,CAAA;AACzD,OAAO,EAAC,kBAAkB,EAAC,MAAM,qBAAqB,CAAA;AAEtD,0GAA0G;AAC1G,MAAM,UAAU,oBAAoB,CAAC,eAA0C;IAC7E,OAAO,CACL,eAAe,EAAE,WAAW,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,KAAK,IAAI;QAC/D,sBAAsB,CAAC,IAAI,CAAC,eAAe,IAAI,EAAE,CAAC,CACnD,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,yBAAyB,CAAC,IAA8D;IACtG,IAAI,CAAC,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAA;IAC7B,IAAI,IAAI,CAAC,KAAK,KAAK,WAAW,CAAC,YAAY,EAAE,CAAC;QAC5C,MAAM,KAAK,GAAG,wBAAwB,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,IAAI,EAAE,CAAC,CAAA;QACvE,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;QAC7C,OAAO,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,QAAQ,IAAI,CAAC,CAAA;IACxD,CAAC;IACD,OAAO,CACL,oBAAoB,CAAC,IAAI,CAAC,KAAoB,CAAC,CAAC,iBAAiB,KAAK,IAAI;QAC1E,oBAAoB,CAAC,IAAI,CAAC,eAAe,CAAC,CAC3C,CAAA;AACH,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,IAAI,gBAAgB,CAAC,QAAQ,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,wBAAwB,CAAC,GAAG,CAAC,KAAK,KAAK;QAAE,OAAO,KAAK,CAAA;IACzG,MAAM,KAAK,GAAG,eAAe,CAAC,QAAQ,EAAE,CAAA;IACxC,OAAO,CACL,kBAAkB,CAAC,KAAK,CAAC,UAAU,CAAC;QACpC,yBAAyB,CAAC,EAAC,KAAK,EAAE,KAAK,CAAC,WAAW,EAAE,eAAe,EAAE,KAAK,CAAC,eAAe,EAAC,CAAC,CAC9F,CAAA;AACH,CAAC","sourcesContent":["/**\n * Whether the dashboard view may show right now: connected Glassly CFW\n * glasses with the dashboard enabled. The page host reads it before showing\n * a page and hides the showing page when it turns false; the miniapp runtime\n * folds it into the designated app's `display.views` capability. Read at\n * decision time, never cached.\n */\nimport {useGlassesStore} from \"../../stores/glasses\"\nimport {SETTINGS, useSettingsStore} from \"../../stores/settings\"\nimport {DeviceTypes} from \"../../types/enums\"\nimport {getModelCapabilities} from \"../../types/hardware\"\nimport {isGlassesConnected} from \"../GlassesReadiness\"\n\n/** The SGC appends this marker after the firmware advertises Glassly CFW capabilities (G2, RayNeo iO). */\nexport function isCfwFirmwareVersion(firmwareVersion: string | null | undefined): boolean {\n return (\n firmwareVersion?.toLowerCase().includes(\"glassly cfw\") === true ||\n /^GLASSLYCFW\\/[0-9]+$/.test(firmwareVersion ?? \"\")\n )\n}\n\n/**\n * Only models that ship a Glassly custom firmware, and only while they run it.\n * Stock firmware on those models and every other model (NIMO, G1, ...) are\n * intentionally excluded.\n */\nexport function isCustomDashboardEligible(info: {model?: string | null; firmwareVersion?: string | null}): boolean {\n if (!info.model) return false\n if (info.model === DeviceTypes.MEMOMIND_ONE) {\n const match = /^GLASSLYCFW\\/([0-9]+)$/.exec(info.firmwareVersion ?? \"\")\n const revision = match ? Number(match[1]) : 0\n return Number.isSafeInteger(revision) && revision >= 7\n }\n return (\n getModelCapabilities(info.model as DeviceTypes).hasCustomFirmware === true &&\n isCfwFirmwareVersion(info.firmwareVersion)\n )\n}\n\nexport function pagesEligible(): boolean {\n if (useSettingsStore.getState().getSetting(SETTINGS.custom_dashboard_enabled.key) === false) return false\n const state = useGlassesStore.getState()\n return (\n isGlassesConnected(state.connection) &&\n isCustomDashboardEligible({model: state.deviceModel, firmwareVersion: state.firmwareVersion})\n )\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"settingsBridge.d.ts","sourceRoot":"","sources":["../../src/services/settingsBridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAC,gBAAgB,EAAE,gBAAgB,EAAC,MAAM,wBAAwB,CAAA;AAK9E,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,OAAO,CAAA;AAEnD,2FAA2F;AAC3F,eAAO,MAAM,oBAAoB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAuB/E,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,sEAAuE,CAAA;AACzG,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEvE,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,IAAI,kBAAkB,CAE3E;AAED,yFAAyF;AACzF,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,oBAAoB,GAAG,IAAI,CAG7E;AAED,wDAAwD;AACxD,wBAAgB,qBAAqB,IAAI,MAAM,EAAE,CAEhD;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,EAAE,CAIrE;AAED,MAAM,MAAM,qBAAqB,GAC7B;IAAC,EAAE,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAC,GACvC;IAAC,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,kBAAkB,GAAG,eAAe,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,CAAA;AAE5E,0FAA0F;AAC1F,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,qBAAqB,CAY3F;AAED,wEAAwE;AACxE,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,wBAAwB;IACxB,SAAS,EAAE,OAAO,CAAA;IAClB,6BAA6B;IAC7B,OAAO,EAAE;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAC,CAAA;IAC3C,qEAAqE;IACrE,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,gGAAgG;AAChG,MAAM,WAAW,WAAW;IAC1B,aAAa,EAAE,OAAO,CAAA;IACtB,gBAAgB,EAAE,MAAM,CAAA;IACxB,YAAY,EAAE,OAAO,CAAA;CACtB;AAcD;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,gBAAgB,EAAE,EAC3B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAAG,SAAS,EACtD,OAAO,EAAE,WAAW,GACnB,sBAAsB,EAAE,CAqB1B"}
1
+ {"version":3,"file":"settingsBridge.d.ts","sourceRoot":"","sources":["../../src/services/settingsBridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAC,gBAAgB,EAAE,gBAAgB,EAAC,MAAM,wBAAwB,CAAA;AAK9E,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,OAAO,CAAA;AAEnD,2FAA2F;AAC3F,eAAO,MAAM,oBAAoB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAwB/E,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,sEAAuE,CAAA;AACzG,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEvE,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,IAAI,kBAAkB,CAE3E;AAED,yFAAyF;AACzF,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,oBAAoB,GAAG,IAAI,CAG7E;AAED,wDAAwD;AACxD,wBAAgB,qBAAqB,IAAI,MAAM,EAAE,CAEhD;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,EAAE,CAIrE;AAED,MAAM,MAAM,qBAAqB,GAC7B;IAAC,EAAE,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAC,GACvC;IAAC,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,kBAAkB,GAAG,eAAe,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,CAAA;AAE5E,0FAA0F;AAC1F,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,qBAAqB,CAY3F;AAED,wEAAwE;AACxE,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,wBAAwB;IACxB,SAAS,EAAE,OAAO,CAAA;IAClB,6BAA6B;IAC7B,OAAO,EAAE;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAC,CAAA;IAC3C,qEAAqE;IACrE,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,gGAAgG;AAChG,MAAM,WAAW,WAAW;IAC1B,aAAa,EAAE,OAAO,CAAA;IACtB,gBAAgB,EAAE,MAAM,CAAA;IACxB,YAAY,EAAE,OAAO,CAAA;CACtB;AAcD;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,gBAAgB,EAAE,EAC3B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAAG,SAAS,EACtD,OAAO,EAAE,WAAW,GACnB,sBAAsB,EAAE,CAqB1B"}
@@ -32,6 +32,7 @@ export const SETTINGS_BRIDGE_KEYS = {
32
32
  notifications_enabled: "write",
33
33
  notifications_popups: "write",
34
34
  notifications_popup_seconds: "write",
35
+ notifications_batch_seconds: "write",
35
36
  notifications_hold_during_apps: "write",
36
37
  notifications_ignore_while_active: "write",
37
38
  notifications_blocklist: "write",
@@ -1 +1 @@
1
- {"version":3,"file":"settingsBridge.js","sourceRoot":"","sources":["../../src/services/settingsBridge.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The settings bridge's vocabulary (session.settings): which engine settings\n * a miniapp with the PLATFORM_SETTINGS permission may read or write, the\n * derived read-only keys the engine computes rather than stores, and the\n * request validation. Pure, so the runtime's handlers stay thin and this can\n * be tested without the native import surface the runtime pulls in.\n *\n * A dashboard miniapp is the usual consumer: it edits the notification,\n * glasses-menu, display-position, brightness and microphone keys the phone's\n * settings screens do, through the store, so persistence, server sync and the\n * Bluetooth sync behave as if a phone screen wrote them. A dashboard's own\n * look is not here; it keeps that in its storage.\n */\n\nimport type {ControllerStatus, PairedController} from \"@glassly/bluetooth-sdk\"\n\nimport {ControllerTypes} from \"../types/enums\"\nimport {hasInput2d, isControllerReady} from \"./controllers\"\n\nexport type SettingsBridgeAccess = \"read\" | \"write\"\n\n/** Stored settings the bridge serves, by key, with what a permitted app may do to each. */\nexport const SETTINGS_BRIDGE_KEYS: Readonly<Record<string, SettingsBridgeAccess>> = {\n // The dashboard's master switch is a phone-side debug knob.\n custom_dashboard_enabled: \"read\",\n contextual_dashboard: \"write\",\n head_up_angle: \"write\",\n // Where the glasses draw; each model's steps are its display.positionRange.\n dashboard_depth: \"write\",\n dashboard_height: \"write\",\n // Device settings the dashboard's glasses Settings page edits.\n brightness: \"write\",\n auto_brightness: \"write\",\n // Per paired model: the store resolves the indexed key.\n preferred_mic: \"write\",\n twelve_hour_time: \"write\",\n menu_apps: \"write\",\n notifications_enabled: \"write\",\n notifications_popups: \"write\",\n notifications_popup_seconds: \"write\",\n notifications_hold_during_apps: \"write\",\n notifications_ignore_while_active: \"write\",\n notifications_blocklist: \"write\",\n // Apps the glasses reported posting notifications; the engine records it.\n notification_seen_apps: \"read\",\n}\n\n/**\n * Read-only keys the engine computes: whether the wearer can see the phone,\n * whether the foreground miniapp is drawing on the glasses, and the paired\n * controllers with their battery and input. They change on their own clocks;\n * the runtime pushes them like stored keys.\n */\nexport const DERIVED_SETTINGS_KEYS = [\"phone_active\", \"foreground_app_on_screen\", \"controllers\"] as const\nexport type DerivedSettingsKey = (typeof DERIVED_SETTINGS_KEYS)[number]\n\nexport function isDerivedSettingsKey(key: string): key is DerivedSettingsKey {\n return (DERIVED_SETTINGS_KEYS as readonly string[]).includes(key)\n}\n\n/** What a permitted app may do with `key`, or null when the bridge does not serve it. */\nexport function settingsBridgeAccess(key: string): SettingsBridgeAccess | null {\n if (isDerivedSettingsKey(key)) return \"read\"\n return SETTINGS_BRIDGE_KEYS[key] ?? null\n}\n\n/** Every key the bridge serves, stored then derived. */\nexport function allSettingsBridgeKeys(): string[] {\n return [...Object.keys(SETTINGS_BRIDGE_KEYS), ...DERIVED_SETTINGS_KEYS]\n}\n\n/**\n * The keys a SETTINGS_GET asks for: everything when `requested` is absent,\n * else the known ones among the requested strings (unknown keys are left\n * out, never rejected).\n */\nexport function selectSettingsBridgeKeys(requested: unknown): string[] {\n if (requested === undefined || requested === null) return allSettingsBridgeKeys()\n if (!Array.isArray(requested)) return []\n return requested.filter((key): key is string => typeof key === \"string\" && settingsBridgeAccess(key) !== null)\n}\n\nexport type SettingsSetValidation =\n | {ok: true; key: string; value: unknown}\n | {ok: false; code: \"INVALID_ARGUMENT\" | \"NOT_PERMITTED\"; message: string}\n\n/** A SETTINGS_SET payload: a writable key and a JSON value (undefined is not a value). */\nexport function validateSettingsSet(payload: Record<string, unknown>): SettingsSetValidation {\n const key = payload.key\n if (typeof key !== \"string\" || !key) {\n return {ok: false, code: \"INVALID_ARGUMENT\", message: \"settings.set requires a key\"}\n }\n const access = settingsBridgeAccess(key)\n if (access === null) return {ok: false, code: \"INVALID_ARGUMENT\", message: `Unknown setting \"${key}\"`}\n if (access !== \"write\") return {ok: false, code: \"NOT_PERMITTED\", message: `Setting \"${key}\" is read-only`}\n if (!(\"value\" in payload) || payload.value === undefined) {\n return {ok: false, code: \"INVALID_ARGUMENT\", message: \"settings.set requires a value\"}\n }\n return {ok: true, key, value: payload.value}\n}\n\n/** One paired controller as the `controllers` derived key serves it. */\nexport interface ControllerSettingEntry {\n id: string\n model: string\n name: string\n /** Delivering input. */\n connected: boolean\n /** level -1 when unknown. */\n battery: {level: number; charging: boolean}\n /** Swipes sideways as well as up and down (the Meta Neural Band). */\n input2d: boolean\n}\n\n/** What the glasses report about the ring linked to them (G2 with the R1 routed through it). */\nexport interface RelayedRing {\n ringConnected: boolean\n ringBatteryLevel: number\n ringCharging: boolean\n}\n\n/**\n * The R1 the glasses' ring report is about: a ring routed through the G2 keeps\n * its phone link for telemetry, so prefer a linked ring, else the first one.\n */\nfunction relayedRingId(\n records: PairedController[],\n statuses: Record<string, ControllerStatus> | undefined,\n): string | undefined {\n const rings = records.filter((record) => record.model === ControllerTypes.R1)\n return (rings.find((ring) => statuses?.[ring.id]?.connected === true) ?? rings[0])?.id\n}\n\n/**\n * One entry per paired record, registry order. While the glasses report a\n * linked ring, that report is that ring's link and battery: a ring routed\n * through the G2 reports there, not to the phone.\n */\nexport function controllersSettingFrom(\n records: PairedController[],\n statuses: Record<string, ControllerStatus> | undefined,\n relayed: RelayedRing,\n): ControllerSettingEntry[] {\n const relayedId = relayed.ringConnected ? relayedRingId(records, statuses) : undefined\n return records.map((record) => {\n const status = statuses?.[record.id]\n let connected = isControllerReady(status)\n let battery = {level: status?.batteryLevel ?? -1, charging: status?.charging === true}\n if (record.id === relayedId) {\n connected = true\n if (relayed.ringBatteryLevel >= 0) {\n battery = {level: relayed.ringBatteryLevel, charging: relayed.ringCharging}\n }\n }\n return {\n id: record.id,\n model: record.model,\n name: record.name,\n connected,\n battery,\n input2d: connected && hasInput2d(record.model),\n }\n })\n}\n"]}
1
+ {"version":3,"file":"settingsBridge.js","sourceRoot":"","sources":["../../src/services/settingsBridge.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The settings bridge's vocabulary (session.settings): which engine settings\n * a miniapp with the PLATFORM_SETTINGS permission may read or write, the\n * derived read-only keys the engine computes rather than stores, and the\n * request validation. Pure, so the runtime's handlers stay thin and this can\n * be tested without the native import surface the runtime pulls in.\n *\n * A dashboard miniapp is the usual consumer: it edits the notification,\n * glasses-menu, display-position, brightness and microphone keys the phone's\n * settings screens do, through the store, so persistence, server sync and the\n * Bluetooth sync behave as if a phone screen wrote them. A dashboard's own\n * look is not here; it keeps that in its storage.\n */\n\nimport type {ControllerStatus, PairedController} from \"@glassly/bluetooth-sdk\"\n\nimport {ControllerTypes} from \"../types/enums\"\nimport {hasInput2d, isControllerReady} from \"./controllers\"\n\nexport type SettingsBridgeAccess = \"read\" | \"write\"\n\n/** Stored settings the bridge serves, by key, with what a permitted app may do to each. */\nexport const SETTINGS_BRIDGE_KEYS: Readonly<Record<string, SettingsBridgeAccess>> = {\n // The dashboard's master switch is a phone-side debug knob.\n custom_dashboard_enabled: \"read\",\n contextual_dashboard: \"write\",\n head_up_angle: \"write\",\n // Where the glasses draw; each model's steps are its display.positionRange.\n dashboard_depth: \"write\",\n dashboard_height: \"write\",\n // Device settings the dashboard's glasses Settings page edits.\n brightness: \"write\",\n auto_brightness: \"write\",\n // Per paired model: the store resolves the indexed key.\n preferred_mic: \"write\",\n twelve_hour_time: \"write\",\n menu_apps: \"write\",\n notifications_enabled: \"write\",\n notifications_popups: \"write\",\n notifications_popup_seconds: \"write\",\n notifications_batch_seconds: \"write\",\n notifications_hold_during_apps: \"write\",\n notifications_ignore_while_active: \"write\",\n notifications_blocklist: \"write\",\n // Apps the glasses reported posting notifications; the engine records it.\n notification_seen_apps: \"read\",\n}\n\n/**\n * Read-only keys the engine computes: whether the wearer can see the phone,\n * whether the foreground miniapp is drawing on the glasses, and the paired\n * controllers with their battery and input. They change on their own clocks;\n * the runtime pushes them like stored keys.\n */\nexport const DERIVED_SETTINGS_KEYS = [\"phone_active\", \"foreground_app_on_screen\", \"controllers\"] as const\nexport type DerivedSettingsKey = (typeof DERIVED_SETTINGS_KEYS)[number]\n\nexport function isDerivedSettingsKey(key: string): key is DerivedSettingsKey {\n return (DERIVED_SETTINGS_KEYS as readonly string[]).includes(key)\n}\n\n/** What a permitted app may do with `key`, or null when the bridge does not serve it. */\nexport function settingsBridgeAccess(key: string): SettingsBridgeAccess | null {\n if (isDerivedSettingsKey(key)) return \"read\"\n return SETTINGS_BRIDGE_KEYS[key] ?? null\n}\n\n/** Every key the bridge serves, stored then derived. */\nexport function allSettingsBridgeKeys(): string[] {\n return [...Object.keys(SETTINGS_BRIDGE_KEYS), ...DERIVED_SETTINGS_KEYS]\n}\n\n/**\n * The keys a SETTINGS_GET asks for: everything when `requested` is absent,\n * else the known ones among the requested strings (unknown keys are left\n * out, never rejected).\n */\nexport function selectSettingsBridgeKeys(requested: unknown): string[] {\n if (requested === undefined || requested === null) return allSettingsBridgeKeys()\n if (!Array.isArray(requested)) return []\n return requested.filter((key): key is string => typeof key === \"string\" && settingsBridgeAccess(key) !== null)\n}\n\nexport type SettingsSetValidation =\n | {ok: true; key: string; value: unknown}\n | {ok: false; code: \"INVALID_ARGUMENT\" | \"NOT_PERMITTED\"; message: string}\n\n/** A SETTINGS_SET payload: a writable key and a JSON value (undefined is not a value). */\nexport function validateSettingsSet(payload: Record<string, unknown>): SettingsSetValidation {\n const key = payload.key\n if (typeof key !== \"string\" || !key) {\n return {ok: false, code: \"INVALID_ARGUMENT\", message: \"settings.set requires a key\"}\n }\n const access = settingsBridgeAccess(key)\n if (access === null) return {ok: false, code: \"INVALID_ARGUMENT\", message: `Unknown setting \"${key}\"`}\n if (access !== \"write\") return {ok: false, code: \"NOT_PERMITTED\", message: `Setting \"${key}\" is read-only`}\n if (!(\"value\" in payload) || payload.value === undefined) {\n return {ok: false, code: \"INVALID_ARGUMENT\", message: \"settings.set requires a value\"}\n }\n return {ok: true, key, value: payload.value}\n}\n\n/** One paired controller as the `controllers` derived key serves it. */\nexport interface ControllerSettingEntry {\n id: string\n model: string\n name: string\n /** Delivering input. */\n connected: boolean\n /** level -1 when unknown. */\n battery: {level: number; charging: boolean}\n /** Swipes sideways as well as up and down (the Meta Neural Band). */\n input2d: boolean\n}\n\n/** What the glasses report about the ring linked to them (G2 with the R1 routed through it). */\nexport interface RelayedRing {\n ringConnected: boolean\n ringBatteryLevel: number\n ringCharging: boolean\n}\n\n/**\n * The R1 the glasses' ring report is about: a ring routed through the G2 keeps\n * its phone link for telemetry, so prefer a linked ring, else the first one.\n */\nfunction relayedRingId(\n records: PairedController[],\n statuses: Record<string, ControllerStatus> | undefined,\n): string | undefined {\n const rings = records.filter((record) => record.model === ControllerTypes.R1)\n return (rings.find((ring) => statuses?.[ring.id]?.connected === true) ?? rings[0])?.id\n}\n\n/**\n * One entry per paired record, registry order. While the glasses report a\n * linked ring, that report is that ring's link and battery: a ring routed\n * through the G2 reports there, not to the phone.\n */\nexport function controllersSettingFrom(\n records: PairedController[],\n statuses: Record<string, ControllerStatus> | undefined,\n relayed: RelayedRing,\n): ControllerSettingEntry[] {\n const relayedId = relayed.ringConnected ? relayedRingId(records, statuses) : undefined\n return records.map((record) => {\n const status = statuses?.[record.id]\n let connected = isControllerReady(status)\n let battery = {level: status?.batteryLevel ?? -1, charging: status?.charging === true}\n if (record.id === relayedId) {\n connected = true\n if (relayed.ringBatteryLevel >= 0) {\n battery = {level: relayed.ringBatteryLevel, charging: relayed.ringCharging}\n }\n }\n return {\n id: record.id,\n model: record.model,\n name: record.name,\n connected,\n battery,\n input2d: connected && hasInput2d(record.model),\n }\n })\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/stores/settings.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,WAAW,EAAwB,MAAM,aAAa,CAAA;AAS9D,OAAO,EAAC,wBAAwB,EAAE,gCAAgC,EAAC,MAAM,wBAAwB,CAAA;AAEjG,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,MAAM,CAAA;IACX,YAAY,EAAE,MAAM,GAAG,CAAA;IACvB,QAAQ,EAAE,OAAO,CAAA;IACjB,YAAY,EAAE,OAAO,CAAA;IAGrB,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAA;IAEjC,QAAQ,CAAC,EAAE,MAAM,GAAG,CAAA;IAEpB,OAAO,EAAE,OAAO,CAAA;IAQhB,mBAAmB,CAAC,EAAE,IAAI,CAAA;IAC1B;;;;;;;;;;;;;;;OAeG;IACH,qBAAqB,CAAC,EAAE,IAAI,CAAA;CAC7B;AAKD,eAAO,MAAM,uBAAuB,IAAI,CAAA;AAExC,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAknCnC,CAAA;AAEV,eAAO,MAAM,eAAe,EAAE,MAAM,EAAuD,CAAA;AAI3F,eAAO,MAAM,sBAAsB,EAAE,MAAM,EA2D1C,CAAA;AAeD,eAAO,MAAM,qBAAqB,EAAE,MAAM,EAEV,CAAA;AAIhC,MAAM,WAAW,aAAa;IAE5B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAE7B,aAAa,EAAE,OAAO,CAAA;IAEtB,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,YAAY,CAAC,EAAE,OAAO,KAAK,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IACzF,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IAC3E,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,GAAG,CAAA;IAEhC,eAAe,EAAE,MAAM,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IAE/C,oBAAoB,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC/C,uBAAuB,EAAE,MAAM,IAAI,CAAA;CACpC;AAuBD,eAAO,MAAM,gBAAgB;;;;;;;;EAsX5B,CAAA;AAED,eAAO,MAAM,UAAU,GAAI,CAAC,GAAG,GAAG,EAAE,KAAK,MAAM,KAAG,CAAC,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,CAI3F,CAAA"}
1
+ {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/stores/settings.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,WAAW,EAAwB,MAAM,aAAa,CAAA;AAS9D,OAAO,EAAC,wBAAwB,EAAE,gCAAgC,EAAC,MAAM,wBAAwB,CAAA;AAEjG,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,MAAM,CAAA;IACX,YAAY,EAAE,MAAM,GAAG,CAAA;IACvB,QAAQ,EAAE,OAAO,CAAA;IACjB,YAAY,EAAE,OAAO,CAAA;IAGrB,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAA;IAEjC,QAAQ,CAAC,EAAE,MAAM,GAAG,CAAA;IAEpB,OAAO,EAAE,OAAO,CAAA;IAQhB,mBAAmB,CAAC,EAAE,IAAI,CAAA;IAC1B;;;;;;;;;;;;;;;OAeG;IACH,qBAAqB,CAAC,EAAE,IAAI,CAAA;CAC7B;AAKD,eAAO,MAAM,uBAAuB,IAAI,CAAA;AAExC,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAynCnC,CAAA;AAEV,eAAO,MAAM,eAAe,EAAE,MAAM,EAAuD,CAAA;AAI3F,eAAO,MAAM,sBAAsB,EAAE,MAAM,EA2D1C,CAAA;AAeD,eAAO,MAAM,qBAAqB,EAAE,MAAM,EAEV,CAAA;AAIhC,MAAM,WAAW,aAAa;IAE5B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAE7B,aAAa,EAAE,OAAO,CAAA;IAEtB,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,YAAY,CAAC,EAAE,OAAO,KAAK,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IACzF,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IAC3E,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,GAAG,CAAA;IAEhC,eAAe,EAAE,MAAM,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IAE/C,oBAAoB,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC/C,uBAAuB,EAAE,MAAM,IAAI,CAAA;CACpC;AAuBD,eAAO,MAAM,gBAAgB;;;;;;;;EAsX5B,CAAA;AAED,eAAO,MAAM,UAAU,GAAI,CAAC,GAAG,GAAG,EAAE,KAAK,MAAM,KAAG,CAAC,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,CAI3F,CAAA"}
@@ -1102,6 +1102,13 @@ export const SETTINGS = {
1102
1102
  saveOnServer: true,
1103
1103
  persist: true,
1104
1104
  },
1105
+ notifications_batch_seconds: {
1106
+ key: "notifications_batch_seconds",
1107
+ defaultValue: () => 0,
1108
+ writable: true,
1109
+ saveOnServer: true,
1110
+ persist: true,
1111
+ },
1105
1112
  // Hold popups while another glasses feature (navigation, a miniapp) is on screen.
1106
1113
  notifications_hold_during_apps: {
1107
1114
  key: "notifications_hold_during_apps",