@mentra/engine 3.2.0-dev.259 → 3.2.0-dev.262
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/facades/displayMirror.js +1 -1
- package/build/facades/displayMirror.js.map +1 -1
- package/build/generated/releaseMetadata.js +5 -5
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/services/AcsMeetingService.d.ts +25 -1
- package/build/services/AcsMeetingService.d.ts.map +1 -1
- package/build/services/AcsMeetingService.js +41 -3
- package/build/services/AcsMeetingService.js.map +1 -1
- package/build/services/DisplayProcessor.d.ts +1 -1
- package/build/services/DisplayProcessor.js.map +1 -1
- package/build/services/LocalDisplayManager.d.ts +0 -4
- package/build/services/LocalDisplayManager.d.ts.map +1 -1
- package/build/services/LocalDisplayManager.js +2 -7
- package/build/services/LocalDisplayManager.js.map +1 -1
- package/build/services/LocalMiniappRuntime.d.ts +39 -24
- package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
- package/build/services/LocalMiniappRuntime.js +293 -123
- package/build/services/LocalMiniappRuntime.js.map +1 -1
- package/build/services/MentraJSRouter.d.ts +5 -9
- package/build/services/MentraJSRouter.d.ts.map +1 -1
- package/build/services/MentraJSRouter.js +5 -9
- package/build/services/MentraJSRouter.js.map +1 -1
- package/build/services/MentraUIRouter.d.ts +9 -12
- package/build/services/MentraUIRouter.d.ts.map +1 -1
- package/build/services/MentraUIRouter.js +9 -12
- package/build/services/MentraUIRouter.js.map +1 -1
- package/build/services/MiniappLauncher.d.ts +6 -9
- package/build/services/MiniappLauncher.d.ts.map +1 -1
- package/build/services/MiniappLauncher.js +6 -9
- package/build/services/MiniappLauncher.js.map +1 -1
- package/build/services/MiniappLiveness.d.ts +11 -0
- package/build/services/MiniappLiveness.d.ts.map +1 -1
- package/build/services/MiniappLiveness.js +11 -0
- package/build/services/MiniappLiveness.js.map +1 -1
- package/build/services/PhoneLocationService.d.ts +2 -2
- package/build/services/PhoneLocationService.d.ts.map +1 -1
- package/build/services/PhoneLocationService.js +5 -9
- package/build/services/PhoneLocationService.js.map +1 -1
- package/build/services/PhoneStreamCoordinator.d.ts +14 -3
- package/build/services/PhoneStreamCoordinator.d.ts.map +1 -1
- package/build/services/PhoneStreamCoordinator.js +28 -6
- package/build/services/PhoneStreamCoordinator.js.map +1 -1
- package/build/services/SoftapCallTransport.d.ts +87 -7
- package/build/services/SoftapCallTransport.d.ts.map +1 -1
- package/build/services/SoftapCallTransport.js +229 -28
- package/build/services/SoftapCallTransport.js.map +1 -1
- package/build/services/slimStreamStatus.d.ts +4 -1
- package/build/services/slimStreamStatus.d.ts.map +1 -1
- package/build/services/slimStreamStatus.js +4 -1
- package/build/services/slimStreamStatus.js.map +1 -1
- package/build/stores/apps.js +2 -2
- package/build/stores/apps.js.map +1 -1
- package/build/utils/miniappGlobals.d.ts +3 -5
- package/build/utils/miniappGlobals.d.ts.map +1 -1
- package/build/utils/miniappGlobals.js +16 -16
- package/build/utils/miniappGlobals.js.map +1 -1
- package/package.json +8 -8
- package/src/facades/displayMirror.ts +1 -1
- package/src/generated/releaseMetadata.ts +5 -5
- package/src/services/AcsMeetingService.ts +51 -3
- package/src/services/DisplayProcessor.ts +1 -1
- package/src/services/LocalDisplayManager.ts +2 -7
- package/src/services/LocalMiniappRuntime.ts +305 -133
- package/src/services/MentraJSRouter.ts +5 -9
- package/src/services/MentraUIRouter.ts +9 -12
- package/src/services/MiniappLauncher.ts +6 -9
- package/src/services/MiniappLiveness.ts +18 -0
- package/src/services/PhoneLocationService.ts +5 -9
- package/src/services/PhoneStreamCoordinator.ts +28 -7
- package/src/services/SoftapCallTransport.ts +274 -32
- package/src/services/slimStreamStatus.ts +4 -1
- package/src/stores/apps.ts +2 -2
- package/src/utils/miniappGlobals.ts +16 -16
|
@@ -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 MentraJSRouter} 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 {isInstalledMiniappAllowed, isLocalMiniappPackageAllowed} from \"../runtime/bootstrap\"\nimport {resolveDevBundleSource} from \"../utils/devMiniappSnapshot\"\nimport {storage} from \"../utils/storage/storage\"\nimport appRegistry, {getLocalAppRunningState, saveLocalAppRunningState} from \"./AppRegistry\"\nimport devServerBridge from \"./DevServerBridge\"\nimport localMiniappRuntime, {type InstalledMiniappManifest} from \"./LocalMiniappRuntime\"\nimport type {MentraJSRouter} from \"./MentraJSRouter\"\n\ninterface LauncherDeps {\n /** The host-constructed router (needs the native Crust binding). */\n router: MentraJSRouter\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}\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(): MentraJSRouter {\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 with no on-disk snapshot, 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: live HTTP, then the last on-disk snapshot if the laptop is gone. ---\n if (devUrl) {\n const source = await resolveDevBundleSource(packageName, devUrl)\n if (source.kind === \"live\") {\n const live = await this.resolveLiveHttp(packageName, source.resolvedUrl, source.manifest, hints)\n if (live) return live\n // Live probe succeeded but the entry fetch failed — still try disk.\n }\n const snapshotVersion =\n source.kind === \"snapshot\" ? source.version : appRegistry.getLatestDevSnapshotVersion(packageName)\n if (snapshotVersion) {\n const snapshot = await this.resolveInstalledBundle(packageName, snapshotVersion)\n if (snapshot) return snapshot\n }\n return null\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 return this.resolveInstalledBundle(packageName, version)\n }\n\n private async resolveLiveHttp(\n packageName: string,\n resolvedUrl: string,\n manifest: {entry?: unknown; permissions?: unknown; [key: string]: unknown},\n hints?: LaunchHints,\n ): Promise<ResolvedBundle | null> {\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 resolveDevBundleSource).\n const base = 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: typeof manifest.name === \"string\" ? manifest.name : undefined,\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: resolvedUrl,\n devPort: this.resolveDevPort(hints?.devPort, packageName),\n }\n }\n\n private async resolveInstalledBundle(packageName: string, version: string): Promise<ResolvedBundle | 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 // Snapshot fallback is file:// — do not wire the sidecar; the laptop is gone.\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 if (!isLocalMiniappPackageAllowed(packageName)) {\n throw new Error(`MiniappLauncher: ${packageName} is disabled by deployment policy`)\n }\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 const version = resolved.installedManifest?.version\n if (\n !isInstalledMiniappAllowed(\n packageName,\n version,\n version ? appRegistry.getReleaseIdentity(packageName, version) : null,\n )\n ) {\n throw new Error(`MiniappLauncher: ${packageName} bundle is not authorized by deployment policy`)\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 })\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 * Cloud apps run on a remote server: the cloud persists which ones are\n * running and resurrects them when the phone reconnects, so a host-app\n * restart brings them back on its own. Local (phone-hosted) miniapps run in\n * the phone's own JS engine — a host-process kill tears down their JSContext\n * and there is no server to resurrect them, so without this they silently\n * stay stopped on the next launch even though the user left 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 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 (!getLocalAppRunningState(app.packageName)) continue\n if (this.isRunning(app.packageName)) continue\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 MentraJSRouter} 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 {isInstalledMiniappAllowed, isLocalMiniappPackageAllowed} from \"../runtime/bootstrap\"\nimport {resolveDevBundleSource} from \"../utils/devMiniappSnapshot\"\nimport {storage} from \"../utils/storage/storage\"\nimport appRegistry, {getLocalAppRunningState, saveLocalAppRunningState} from \"./AppRegistry\"\nimport devServerBridge from \"./DevServerBridge\"\nimport localMiniappRuntime, {type InstalledMiniappManifest} from \"./LocalMiniappRuntime\"\nimport type {MentraJSRouter} from \"./MentraJSRouter\"\n\ninterface LauncherDeps {\n /** The host-constructed router (needs the native Crust binding). */\n router: MentraJSRouter\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}\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(): MentraJSRouter {\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 with no on-disk snapshot, 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: live HTTP, then the last on-disk snapshot if the laptop is gone. ---\n if (devUrl) {\n const source = await resolveDevBundleSource(packageName, devUrl)\n if (source.kind === \"live\") {\n const live = await this.resolveLiveHttp(packageName, source.resolvedUrl, source.manifest, hints)\n if (live) return live\n // Live probe succeeded but the entry fetch failed — still try disk.\n }\n const snapshotVersion =\n source.kind === \"snapshot\" ? source.version : appRegistry.getLatestDevSnapshotVersion(packageName)\n if (snapshotVersion) {\n const snapshot = await this.resolveInstalledBundle(packageName, snapshotVersion)\n if (snapshot) return snapshot\n }\n return null\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 return this.resolveInstalledBundle(packageName, version)\n }\n\n private async resolveLiveHttp(\n packageName: string,\n resolvedUrl: string,\n manifest: {entry?: unknown; permissions?: unknown; [key: string]: unknown},\n hints?: LaunchHints,\n ): Promise<ResolvedBundle | null> {\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 resolveDevBundleSource).\n const base = 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: typeof manifest.name === \"string\" ? manifest.name : undefined,\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: resolvedUrl,\n devPort: this.resolveDevPort(hints?.devPort, packageName),\n }\n }\n\n private async resolveInstalledBundle(packageName: string, version: string): Promise<ResolvedBundle | 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 // Snapshot fallback is file:// — do not wire the sidecar; the laptop is gone.\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 if (!isLocalMiniappPackageAllowed(packageName)) {\n throw new Error(`MiniappLauncher: ${packageName} is disabled by deployment policy`)\n }\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 const version = resolved.installedManifest?.version\n if (\n !isInstalledMiniappAllowed(\n packageName,\n version,\n version ? appRegistry.getReleaseIdentity(packageName, version) : null,\n )\n ) {\n throw new Error(`MiniappLauncher: ${packageName} bundle is not authorized by deployment policy`)\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 })\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 * Every miniapp runs as local JavaScript in the Mentra App on the phone.\n * A host-process kill tears down its JSContext, so without this restoration\n * it stays stopped on the next launch even though the user left it 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 running flag if ensureRunning fails (for example, bundle resolution,\n * deployment policy, or context spawning) so it does not 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 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 (!getLocalAppRunningState(app.packageName)) continue\n if (this.isRunning(app.packageName)) continue\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"]}
|
|
@@ -2,6 +2,17 @@ export interface MiniappPingLivenessDecision {
|
|
|
2
2
|
shouldUnregister: boolean;
|
|
3
3
|
unansweredPingRounds: number;
|
|
4
4
|
}
|
|
5
|
+
/**
|
|
6
|
+
* SoftAP join can spend a minute in native `prepareAgent` before the glasses
|
|
7
|
+
* hotspot even turns on. The JS bundle stays alive but may miss pongs while
|
|
8
|
+
* ACS is on the native thread. Unregistering for missed pings in that window
|
|
9
|
+
* cancels the join and leaves the UI stuck on the hotspot row.
|
|
10
|
+
*/
|
|
11
|
+
export declare function shouldHoldMiniappPingLiveness(args: {
|
|
12
|
+
packageName: string;
|
|
13
|
+
softapPackageName?: string | null;
|
|
14
|
+
softapCancelled?: boolean;
|
|
15
|
+
}): boolean;
|
|
5
16
|
/**
|
|
6
17
|
* Advance liveness by one scheduled ping round.
|
|
7
18
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MiniappLiveness.d.ts","sourceRoot":"","sources":["../../src/services/MiniappLiveness.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,2BAA2B;IAC1C,gBAAgB,EAAE,OAAO,CAAA;IACzB,oBAAoB,EAAE,MAAM,CAAA;CAC7B;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CACxC,oBAAoB,EAAE,MAAM,EAC5B,gBAAgB,EAAE,MAAM,GACvB,2BAA2B,CAS7B"}
|
|
1
|
+
{"version":3,"file":"MiniappLiveness.d.ts","sourceRoot":"","sources":["../../src/services/MiniappLiveness.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,2BAA2B;IAC1C,gBAAgB,EAAE,OAAO,CAAA;IACzB,oBAAoB,EAAE,MAAM,CAAA;CAC7B;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE;IAClD,WAAW,EAAE,MAAM,CAAA;IACnB,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACjC,eAAe,CAAC,EAAE,OAAO,CAAA;CAC1B,GAAG,OAAO,CAMV;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CACxC,oBAAoB,EAAE,MAAM,EAC5B,gBAAgB,EAAE,MAAM,GACvB,2BAA2B,CAS7B"}
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SoftAP join can spend a minute in native `prepareAgent` before the glasses
|
|
3
|
+
* hotspot even turns on. The JS bundle stays alive but may miss pongs while
|
|
4
|
+
* ACS is on the native thread. Unregistering for missed pings in that window
|
|
5
|
+
* cancels the join and leaves the UI stuck on the hotspot row.
|
|
6
|
+
*/
|
|
7
|
+
export function shouldHoldMiniappPingLiveness(args) {
|
|
8
|
+
return Boolean(args.softapPackageName &&
|
|
9
|
+
args.softapPackageName === args.packageName &&
|
|
10
|
+
!args.softapCancelled);
|
|
11
|
+
}
|
|
1
12
|
/**
|
|
2
13
|
* Advance liveness by one scheduled ping round.
|
|
3
14
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MiniappLiveness.js","sourceRoot":"","sources":["../../src/services/MiniappLiveness.ts"],"names":[],"mappings":"AAKA;;;;;GAKG;AACH,MAAM,UAAU,0BAA0B,CACxC,oBAA4B,EAC5B,gBAAwB;IAExB,IAAI,oBAAoB,IAAI,gBAAgB,EAAE,CAAC;QAC7C,OAAO,EAAC,gBAAgB,EAAE,IAAI,EAAE,oBAAoB,EAAC,CAAA;IACvD,CAAC;IAED,OAAO;QACL,gBAAgB,EAAE,KAAK;QACvB,oBAAoB,EAAE,oBAAoB,GAAG,CAAC;KAC/C,CAAA;AACH,CAAC","sourcesContent":["export interface MiniappPingLivenessDecision {\n shouldUnregister: boolean\n unansweredPingRounds: number\n}\n\n/**\n * Advance liveness by one scheduled ping round.\n *\n * Counting actual rounds avoids treating time while React Native's scheduler\n * was paused in the background as pings the miniapp failed to answer.\n */\nexport function advanceMiniappPingLiveness(\n unansweredPingRounds: number,\n timeoutThreshold: number,\n): MiniappPingLivenessDecision {\n if (unansweredPingRounds >= timeoutThreshold) {\n return {shouldUnregister: true, unansweredPingRounds}\n }\n\n return {\n shouldUnregister: false,\n unansweredPingRounds: unansweredPingRounds + 1,\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"MiniappLiveness.js","sourceRoot":"","sources":["../../src/services/MiniappLiveness.ts"],"names":[],"mappings":"AAKA;;;;;GAKG;AACH,MAAM,UAAU,6BAA6B,CAAC,IAI7C;IACC,OAAO,OAAO,CACZ,IAAI,CAAC,iBAAiB;QACpB,IAAI,CAAC,iBAAiB,KAAK,IAAI,CAAC,WAAW;QAC3C,CAAC,IAAI,CAAC,eAAe,CACxB,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,0BAA0B,CACxC,oBAA4B,EAC5B,gBAAwB;IAExB,IAAI,oBAAoB,IAAI,gBAAgB,EAAE,CAAC;QAC7C,OAAO,EAAC,gBAAgB,EAAE,IAAI,EAAE,oBAAoB,EAAC,CAAA;IACvD,CAAC;IAED,OAAO;QACL,gBAAgB,EAAE,KAAK;QACvB,oBAAoB,EAAE,oBAAoB,GAAG,CAAC;KAC/C,CAAA;AACH,CAAC","sourcesContent":["export interface MiniappPingLivenessDecision {\n shouldUnregister: boolean\n unansweredPingRounds: number\n}\n\n/**\n * SoftAP join can spend a minute in native `prepareAgent` before the glasses\n * hotspot even turns on. The JS bundle stays alive but may miss pongs while\n * ACS is on the native thread. Unregistering for missed pings in that window\n * cancels the join and leaves the UI stuck on the hotspot row.\n */\nexport function shouldHoldMiniappPingLiveness(args: {\n packageName: string\n softapPackageName?: string | null\n softapCancelled?: boolean\n}): boolean {\n return Boolean(\n args.softapPackageName &&\n args.softapPackageName === args.packageName &&\n !args.softapCancelled,\n )\n}\n\n/**\n * Advance liveness by one scheduled ping round.\n *\n * Counting actual rounds avoids treating time while React Native's scheduler\n * was paused in the background as pings the miniapp failed to answer.\n */\nexport function advanceMiniappPingLiveness(\n unansweredPingRounds: number,\n timeoutThreshold: number,\n): MiniappPingLivenessDecision {\n if (unansweredPingRounds >= timeoutThreshold) {\n return {shouldUnregister: true, unansweredPingRounds}\n }\n\n return {\n shouldUnregister: false,\n unansweredPingRounds: unansweredPingRounds + 1,\n }\n}\n"]}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Phone location service — engine-owned. Owns the background phone-GPS task: the
|
|
3
3
|
* accuracy-tier control (`setLocationTier`), the accuracy mapping, and the
|
|
4
|
-
* `expo-task-manager` background task that
|
|
5
|
-
* local miniapps (`location_update`). Cloud upload was V1 (removed).
|
|
4
|
+
* `expo-task-manager` background task that forwards the first fix in each batch
|
|
5
|
+
* to subscribed local miniapps (`location_update`). Cloud upload was V1 (removed).
|
|
6
6
|
*
|
|
7
7
|
* This used to be the host-injected `locationTier` runtime hook + a MantleManager
|
|
8
8
|
* `TaskManager.defineTask`. It's device/OS plumbing (no UI), so it moved into engine:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PhoneLocationService.d.ts","sourceRoot":"","sources":["../../src/services/PhoneLocationService.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AAKzC,eAAO,MAAM,kBAAkB,0BAA0B,CAAA;
|
|
1
|
+
{"version":3,"file":"PhoneLocationService.d.ts","sourceRoot":"","sources":["../../src/services/PhoneLocationService.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AAKzC,eAAO,MAAM,kBAAkB,0BAA0B,CAAA;AA0BzD,iFAAiF;AACjF,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,QAAQ,CAAC,gBAAgB,CA4B3F;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,KAAK,GAAG,SAAS,GAAG,KAAK,GAAG,MAAM,GAAG,UAAU,GAAG,MAAM,GAC7D,OAAO,CAAC,IAAI,CAAC,CAmBf;AAED,wDAAwD;AACxD,wBAAgB,iBAAiB,IAAI,IAAI,CAExC;AAED,eAAO,MAAM,oBAAoB;;;;;CAKhC,CAAA"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Phone location service — engine-owned. Owns the background phone-GPS task: the
|
|
3
3
|
* accuracy-tier control (`setLocationTier`), the accuracy mapping, and the
|
|
4
|
-
* `expo-task-manager` background task that
|
|
5
|
-
* local miniapps (`location_update`). Cloud upload was V1 (removed).
|
|
4
|
+
* `expo-task-manager` background task that forwards the first fix in each batch
|
|
5
|
+
* to subscribed local miniapps (`location_update`). Cloud upload was V1 (removed).
|
|
6
6
|
*
|
|
7
7
|
* This used to be the host-injected `locationTier` runtime hook + a MantleManager
|
|
8
8
|
* `TaskManager.defineTask`. It's device/OS plumbing (no UI), so it moved into engine:
|
|
@@ -18,8 +18,7 @@ import * as Location from "expo-location";
|
|
|
18
18
|
import * as TaskManager from "expo-task-manager";
|
|
19
19
|
import localMiniappRuntime from "./LocalMiniappRuntime";
|
|
20
20
|
export const LOCATION_TASK_NAME = "handleLocationUpdates";
|
|
21
|
-
// Background location task —
|
|
22
|
-
// miniapps. (The v1 SocketComms leg was already removed/commented before the move.)
|
|
21
|
+
// Background location task — forwards the first fix from each non-empty batch.
|
|
23
22
|
TaskManager.defineTask(LOCATION_TASK_NAME, async ({ data, error }) => {
|
|
24
23
|
if (error) {
|
|
25
24
|
// OS-level failure (permission revoked, GPS unavailable, …) — log it so
|
|
@@ -33,11 +32,8 @@ TaskManager.defineTask(LOCATION_TASK_NAME, async ({ data, error }) => {
|
|
|
33
32
|
return;
|
|
34
33
|
}
|
|
35
34
|
const first = locs[0];
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
// Local miniapps get the update directly (the cloud relay never reaches
|
|
39
|
-
// __phone__). The Cloud V1 upload that used to run here was removed with the
|
|
40
|
-
// V1 ripout (issue #3392); a V2 location channel is separate product work.
|
|
35
|
+
// Deliver directly to local miniapps. The Cloud V1 upload that used to run
|
|
36
|
+
// here was removed with the V1 ripout (issue #3392).
|
|
41
37
|
localMiniappRuntime.forwardEvent("location_update", {
|
|
42
38
|
lat: first.coords.latitude,
|
|
43
39
|
lng: first.coords.longitude,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PhoneLocationService.js","sourceRoot":"","sources":["../../src/services/PhoneLocationService.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AACzC,OAAO,KAAK,WAAW,MAAM,mBAAmB,CAAA;AAEhD,OAAO,mBAAmB,MAAM,uBAAuB,CAAA;AAEvD,MAAM,CAAC,MAAM,kBAAkB,GAAG,uBAAuB,CAAA;AAEzD
|
|
1
|
+
{"version":3,"file":"PhoneLocationService.js","sourceRoot":"","sources":["../../src/services/PhoneLocationService.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Phone location service — engine-owned. Owns the background phone-GPS task: the\n * accuracy-tier control (`setLocationTier`), the accuracy mapping, and the\n * `expo-task-manager` background task that forwards the first fix in each batch\n * to subscribed local miniapps (`location_update`). Cloud upload was V1 (removed).\n *\n * This used to be the host-injected `locationTier` runtime hook + a MantleManager\n * `TaskManager.defineTask`. It's device/OS plumbing (no UI), so it moved into engine:\n * the runtime drives `setLocationTier` directly off miniapp demand, and any host —\n * including a bare OEM — gets phone location without wiring a hook. The host keeps the\n * permission *UI* (it gates `setLocationTier` on the OS permission before calling).\n *\n * The task is registered at module load (the export from `index.ts` evaluates this\n * file as soon as `@mentra/engine` is imported), matching the old MantleManager\n * top-level `defineTask` timing — including on a headless background relaunch.\n */\nimport * as Location from \"expo-location\"\nimport * as TaskManager from \"expo-task-manager\"\n\nimport localMiniappRuntime from \"./LocalMiniappRuntime\"\n\nexport const LOCATION_TASK_NAME = \"handleLocationUpdates\"\n\n// Background location task — forwards the first fix from each non-empty batch.\nTaskManager.defineTask<{locations?: Location.LocationObject[]}>(LOCATION_TASK_NAME, async ({data, error}) => {\n if (error) {\n // OS-level failure (permission revoked, GPS unavailable, …) — log it so\n // background location dropouts are diagnosable.\n console.warn(\"ISLAND: LOCATION: background task error:\", error)\n return\n }\n const locs = data?.locations ?? []\n if (locs.length === 0) {\n console.log(\"ISLAND: LOCATION: No locations received\")\n return\n }\n const first = locs[0]!\n // Deliver directly to local miniapps. The Cloud V1 upload that used to run\n // here was removed with the V1 ripout (issue #3392).\n localMiniappRuntime.forwardEvent(\"location_update\", {\n lat: first.coords.latitude,\n lng: first.coords.longitude,\n accuracy: first.coords.accuracy ?? undefined,\n timestamp: first.timestamp,\n })\n})\n\n/** Map a MentraOS location tier/accuracy string to an expo-location accuracy. */\nexport function getLocationAccuracy(accuracy: string | undefined): Location.LocationAccuracy {\n switch (accuracy) {\n // Aggregate miniapp tiers (LocalMiniappRuntime.recomputeLocation → \"passive\" |\n // \"low\" | \"high\" | \"realtime\"). Previously only \"realtime\" mapped; \"passive\"/\"low\"/\n // \"high\" fell through to Lowest, so a miniapp asking for high-rate GPS got the\n // coarsest accuracy.\n case \"realtime\":\n return Location.LocationAccuracy.BestForNavigation\n case \"high\":\n return Location.LocationAccuracy.High\n case \"low\":\n return Location.LocationAccuracy.Low\n case \"passive\":\n return Location.LocationAccuracy.Lowest\n // Accuracy-string vocabulary (persisted location_tier setting / boot path).\n case \"tenMeters\":\n return Location.LocationAccuracy.High\n case \"hundredMeters\":\n return Location.LocationAccuracy.Balanced\n case \"kilometer\":\n return Location.LocationAccuracy.Low\n case \"threeKilometers\":\n return Location.LocationAccuracy.Lowest\n case \"reduced\":\n return Location.LocationAccuracy.Lowest\n default:\n return Location.LocationAccuracy.Lowest\n }\n}\n\n/**\n * Apply a location tier (the aggregate of what miniapps request). \"off\" means no app\n * is asking — stop the task so the OS can power GPS down; anything else (re)starts the\n * background task at the matching accuracy. Callers gate this on the OS location\n * permission (host UI concern).\n */\nexport async function setLocationTier(\n tier: \"off\" | \"passive\" | \"low\" | \"high\" | \"realtime\" | string,\n): Promise<void> {\n console.log(\"ISLAND: setLocationTier()\", tier)\n try {\n const isRegistered = await Location.hasStartedLocationUpdatesAsync(LOCATION_TASK_NAME).catch(() => false)\n if (isRegistered) {\n await Location.stopLocationUpdatesAsync(LOCATION_TASK_NAME)\n }\n if (tier === \"off\") {\n console.log(\"ISLAND: setLocationTier() stopped — no active subscribers\")\n return\n }\n await Location.startLocationUpdatesAsync(LOCATION_TASK_NAME, {\n accuracy: getLocationAccuracy(tier),\n pausesUpdatesAutomatically: false,\n })\n console.log(\"ISLAND: setLocationTier() success —\", tier)\n } catch (error) {\n console.log(\"ISLAND: Error setting location tier\", error)\n }\n}\n\n/** Stop the background location task (host cleanup). */\nexport function stopPhoneLocation(): void {\n Location.stopLocationUpdatesAsync(LOCATION_TASK_NAME).catch(() => {})\n}\n\nexport const phoneLocationService = {\n LOCATION_TASK_NAME,\n getLocationAccuracy,\n setLocationTier,\n stopPhoneLocation,\n}\n"]}
|
|
@@ -23,8 +23,7 @@
|
|
|
23
23
|
*
|
|
24
24
|
* Important: this is the ONLY place that mints `streamId`s for phone-owned
|
|
25
25
|
* streams. We use a `phone-` prefix so they're trivially distinguishable from
|
|
26
|
-
* cloud-minted IDs in logs
|
|
27
|
-
* the legacy path.
|
|
26
|
+
* cloud-minted managed-stream resource IDs in logs.
|
|
28
27
|
*
|
|
29
28
|
* BLE link loss is a SUSPENDED state, not a failure:
|
|
30
29
|
* ASG owns the ten-second phone/controller liveness deadline. The coordinator
|
|
@@ -151,9 +150,12 @@ export declare class PhoneStreamCoordinator {
|
|
|
151
150
|
* A stream was torn down while the BLE link was down, so the glasses never
|
|
152
151
|
* got `stopStream`. Sent on the next reconnect (if no new stream has claimed
|
|
153
152
|
* the slot) so a publisher that outlived its input does not keep pushing
|
|
154
|
-
* until its own watchdog fires.
|
|
153
|
+
* until its own watchdog fires. `generation` belongs to the publisher that
|
|
154
|
+
* queued it; a later SoftAP media hop must not inherit this stop.
|
|
155
155
|
*/
|
|
156
156
|
private pendingBleStop;
|
|
157
|
+
/** Bumped each time an unmanaged/managed publisher claims the slot. */
|
|
158
|
+
private publisherGeneration;
|
|
157
159
|
/**
|
|
158
160
|
* Serializes state transitions (start, stop, teardown). Without it, a
|
|
159
161
|
* second `start*` racing with the first can pass the `this.current === null`
|
|
@@ -203,6 +205,15 @@ export declare class PhoneStreamCoordinator {
|
|
|
203
205
|
startUnmanaged(packageName: string, opts: StartUnmanagedOptions): Promise<StreamPublisherStartResult>;
|
|
204
206
|
startManaged(packageName: string, opts: StartManagedOptions): Promise<ManagedStartResult>;
|
|
205
207
|
stop(packageName: string, streamId?: string): Promise<void>;
|
|
208
|
+
/**
|
|
209
|
+
* Drop a deferred BLE `stopStream` that belonged to a publisher that is gone.
|
|
210
|
+
*
|
|
211
|
+
* SoftAP recovery destroys generation N and rebuilds N+1. If failSuspended already
|
|
212
|
+
* tore the publisher down after `glassesGraceMs`, `stop()` is a no-op but a pending
|
|
213
|
+
* stop would still flush into the new hop on reconnect. Call this from SoftAP
|
|
214
|
+
* `stopPublishing` so the deferred command dies with its generation.
|
|
215
|
+
*/
|
|
216
|
+
discardPendingBleStop(): void;
|
|
206
217
|
/**
|
|
207
218
|
* Called by MantleManager when a `stream_status` event arrives from glasses
|
|
208
219
|
* and the registry says it's phone-owned.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PhoneStreamCoordinator.d.ts","sourceRoot":"","sources":["../../src/services/PhoneStreamCoordinator.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"PhoneStreamCoordinator.d.ts","sourceRoot":"","sources":["../../src/services/PhoneStreamCoordinator.ts"],"names":[],"mappings":"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"}
|
|
@@ -23,8 +23,7 @@
|
|
|
23
23
|
*
|
|
24
24
|
* Important: this is the ONLY place that mints `streamId`s for phone-owned
|
|
25
25
|
* streams. We use a `phone-` prefix so they're trivially distinguishable from
|
|
26
|
-
* cloud-minted IDs in logs
|
|
27
|
-
* the legacy path.
|
|
26
|
+
* cloud-minted managed-stream resource IDs in logs.
|
|
28
27
|
*
|
|
29
28
|
* BLE link loss is a SUSPENDED state, not a failure:
|
|
30
29
|
* ASG owns the ten-second phone/controller liveness deadline. The coordinator
|
|
@@ -99,9 +98,12 @@ export class PhoneStreamCoordinator {
|
|
|
99
98
|
* A stream was torn down while the BLE link was down, so the glasses never
|
|
100
99
|
* got `stopStream`. Sent on the next reconnect (if no new stream has claimed
|
|
101
100
|
* the slot) so a publisher that outlived its input does not keep pushing
|
|
102
|
-
* until its own watchdog fires.
|
|
101
|
+
* until its own watchdog fires. `generation` belongs to the publisher that
|
|
102
|
+
* queued it; a later SoftAP media hop must not inherit this stop.
|
|
103
103
|
*/
|
|
104
104
|
pendingBleStop = null;
|
|
105
|
+
/** Bumped each time an unmanaged/managed publisher claims the slot. */
|
|
106
|
+
publisherGeneration = 0;
|
|
105
107
|
/**
|
|
106
108
|
* Serializes state transitions (start, stop, teardown). Without it, a
|
|
107
109
|
* second `start*` racing with the first can pass the `this.current === null`
|
|
@@ -213,6 +215,7 @@ export class PhoneStreamCoordinator {
|
|
|
213
215
|
throw new StreamConflictError("STREAM_ALREADY_ACTIVE", `A ${this.current.kind} stream is already active. Stop it before starting a new one.`);
|
|
214
216
|
}
|
|
215
217
|
const streamId = this.mintId("u");
|
|
218
|
+
++this.publisherGeneration;
|
|
216
219
|
const entry = {
|
|
217
220
|
kind: "unmanaged",
|
|
218
221
|
streamId,
|
|
@@ -292,6 +295,7 @@ export class PhoneStreamCoordinator {
|
|
|
292
295
|
// and joins instead of double-provisioning.
|
|
293
296
|
const provision = await provisionManagedStream(opts.restreamDestinations);
|
|
294
297
|
const streamId = this.mintId("m");
|
|
298
|
+
++this.publisherGeneration;
|
|
295
299
|
let ingestUrl;
|
|
296
300
|
try {
|
|
297
301
|
ingestUrl = pickIngestUrl(provision, opts.ingest);
|
|
@@ -338,7 +342,7 @@ export class PhoneStreamCoordinator {
|
|
|
338
342
|
await this.teardownLocked("relay_failed");
|
|
339
343
|
}).catch((cleanupError) => console.warn("[STREAM] relay cleanup failed", cleanupError));
|
|
340
344
|
}, () => this.linkSource.isConnected(), () => {
|
|
341
|
-
this.pendingBleStop = { streamId, hotspot: true };
|
|
345
|
+
this.pendingBleStop = { streamId, hotspot: true, generation: this.publisherGeneration };
|
|
342
346
|
this.attachLink();
|
|
343
347
|
});
|
|
344
348
|
}
|
|
@@ -433,6 +437,22 @@ export class PhoneStreamCoordinator {
|
|
|
433
437
|
await this.teardownLocked("explicit_stop");
|
|
434
438
|
});
|
|
435
439
|
}
|
|
440
|
+
/**
|
|
441
|
+
* Drop a deferred BLE `stopStream` that belonged to a publisher that is gone.
|
|
442
|
+
*
|
|
443
|
+
* SoftAP recovery destroys generation N and rebuilds N+1. If failSuspended already
|
|
444
|
+
* tore the publisher down after `glassesGraceMs`, `stop()` is a no-op but a pending
|
|
445
|
+
* stop would still flush into the new hop on reconnect. Call this from SoftAP
|
|
446
|
+
* `stopPublishing` so the deferred command dies with its generation.
|
|
447
|
+
*/
|
|
448
|
+
discardPendingBleStop() {
|
|
449
|
+
const pending = this.pendingBleStop;
|
|
450
|
+
if (!pending)
|
|
451
|
+
return;
|
|
452
|
+
pending.discarded = true;
|
|
453
|
+
this.pendingBleStop = null;
|
|
454
|
+
this.detachLinkIfIdle();
|
|
455
|
+
}
|
|
436
456
|
/**
|
|
437
457
|
* Called by MantleManager when a `stream_status` event arrives from glasses
|
|
438
458
|
* and the registry says it's phone-owned.
|
|
@@ -573,10 +593,12 @@ export class PhoneStreamCoordinator {
|
|
|
573
593
|
}
|
|
574
594
|
async flushPendingBleStop() {
|
|
575
595
|
const pending = this.pendingBleStop;
|
|
576
|
-
if (!pending || this.current || !this.linkSource.isConnected())
|
|
596
|
+
if (!pending || pending.discarded || this.current || !this.linkSource.isConnected())
|
|
577
597
|
return;
|
|
578
598
|
console.info("[STREAM] BLE link back; sending deferred stopStream", pending);
|
|
579
599
|
await BluetoothSdk.stopStream();
|
|
600
|
+
if (pending.discarded || this.pendingBleStop !== pending || this.current)
|
|
601
|
+
return;
|
|
580
602
|
if (pending.hotspot) {
|
|
581
603
|
const result = await BluetoothSdk.setHotspotState(false);
|
|
582
604
|
if (result.state !== "disabled")
|
|
@@ -829,7 +851,7 @@ export class PhoneStreamCoordinator {
|
|
|
829
851
|
// lock for the native timeout). Defer it to the next reconnect instead.
|
|
830
852
|
const linkUp = this.linkSource.isConnected();
|
|
831
853
|
if (sendBleStop && !linkUp) {
|
|
832
|
-
this.pendingBleStop = { streamId: entry.streamId, hotspot: entry.kind === "managed" && !!entry.relay };
|
|
854
|
+
this.pendingBleStop = { streamId: entry.streamId, hotspot: entry.kind === "managed" && !!entry.relay, generation: this.publisherGeneration };
|
|
833
855
|
console.warn("[STREAM] BLE link down during teardown; stopStream deferred", {
|
|
834
856
|
streamId: entry.streamId,
|
|
835
857
|
reason,
|