@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.
Files changed (73) hide show
  1. package/build/facades/displayMirror.js +1 -1
  2. package/build/facades/displayMirror.js.map +1 -1
  3. package/build/generated/releaseMetadata.js +5 -5
  4. package/build/generated/releaseMetadata.js.map +1 -1
  5. package/build/services/AcsMeetingService.d.ts +25 -1
  6. package/build/services/AcsMeetingService.d.ts.map +1 -1
  7. package/build/services/AcsMeetingService.js +41 -3
  8. package/build/services/AcsMeetingService.js.map +1 -1
  9. package/build/services/DisplayProcessor.d.ts +1 -1
  10. package/build/services/DisplayProcessor.js.map +1 -1
  11. package/build/services/LocalDisplayManager.d.ts +0 -4
  12. package/build/services/LocalDisplayManager.d.ts.map +1 -1
  13. package/build/services/LocalDisplayManager.js +2 -7
  14. package/build/services/LocalDisplayManager.js.map +1 -1
  15. package/build/services/LocalMiniappRuntime.d.ts +39 -24
  16. package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
  17. package/build/services/LocalMiniappRuntime.js +293 -123
  18. package/build/services/LocalMiniappRuntime.js.map +1 -1
  19. package/build/services/MentraJSRouter.d.ts +5 -9
  20. package/build/services/MentraJSRouter.d.ts.map +1 -1
  21. package/build/services/MentraJSRouter.js +5 -9
  22. package/build/services/MentraJSRouter.js.map +1 -1
  23. package/build/services/MentraUIRouter.d.ts +9 -12
  24. package/build/services/MentraUIRouter.d.ts.map +1 -1
  25. package/build/services/MentraUIRouter.js +9 -12
  26. package/build/services/MentraUIRouter.js.map +1 -1
  27. package/build/services/MiniappLauncher.d.ts +6 -9
  28. package/build/services/MiniappLauncher.d.ts.map +1 -1
  29. package/build/services/MiniappLauncher.js +6 -9
  30. package/build/services/MiniappLauncher.js.map +1 -1
  31. package/build/services/MiniappLiveness.d.ts +11 -0
  32. package/build/services/MiniappLiveness.d.ts.map +1 -1
  33. package/build/services/MiniappLiveness.js +11 -0
  34. package/build/services/MiniappLiveness.js.map +1 -1
  35. package/build/services/PhoneLocationService.d.ts +2 -2
  36. package/build/services/PhoneLocationService.d.ts.map +1 -1
  37. package/build/services/PhoneLocationService.js +5 -9
  38. package/build/services/PhoneLocationService.js.map +1 -1
  39. package/build/services/PhoneStreamCoordinator.d.ts +14 -3
  40. package/build/services/PhoneStreamCoordinator.d.ts.map +1 -1
  41. package/build/services/PhoneStreamCoordinator.js +28 -6
  42. package/build/services/PhoneStreamCoordinator.js.map +1 -1
  43. package/build/services/SoftapCallTransport.d.ts +87 -7
  44. package/build/services/SoftapCallTransport.d.ts.map +1 -1
  45. package/build/services/SoftapCallTransport.js +229 -28
  46. package/build/services/SoftapCallTransport.js.map +1 -1
  47. package/build/services/slimStreamStatus.d.ts +4 -1
  48. package/build/services/slimStreamStatus.d.ts.map +1 -1
  49. package/build/services/slimStreamStatus.js +4 -1
  50. package/build/services/slimStreamStatus.js.map +1 -1
  51. package/build/stores/apps.js +2 -2
  52. package/build/stores/apps.js.map +1 -1
  53. package/build/utils/miniappGlobals.d.ts +3 -5
  54. package/build/utils/miniappGlobals.d.ts.map +1 -1
  55. package/build/utils/miniappGlobals.js +16 -16
  56. package/build/utils/miniappGlobals.js.map +1 -1
  57. package/package.json +8 -8
  58. package/src/facades/displayMirror.ts +1 -1
  59. package/src/generated/releaseMetadata.ts +5 -5
  60. package/src/services/AcsMeetingService.ts +51 -3
  61. package/src/services/DisplayProcessor.ts +1 -1
  62. package/src/services/LocalDisplayManager.ts +2 -7
  63. package/src/services/LocalMiniappRuntime.ts +305 -133
  64. package/src/services/MentraJSRouter.ts +5 -9
  65. package/src/services/MentraUIRouter.ts +9 -12
  66. package/src/services/MiniappLauncher.ts +6 -9
  67. package/src/services/MiniappLiveness.ts +18 -0
  68. package/src/services/PhoneLocationService.ts +5 -9
  69. package/src/services/PhoneStreamCoordinator.ts +28 -7
  70. package/src/services/SoftapCallTransport.ts +274 -32
  71. package/src/services/slimStreamStatus.ts +4 -1
  72. package/src/stores/apps.ts +2 -2
  73. package/src/utils/miniappGlobals.ts +16 -16
@@ -50,14 +50,13 @@ interface BoundWebView {
50
50
  }
51
51
 
52
52
  /**
53
- * NOTE: the WebView host heartbeat was removed when the lifecycle
54
- * inversion landed. The current model is **at most one WebView at a
55
- * time, foreground UI with an always-on background JSContext** (same
56
- * shape as the cloud-WebView miniapps). User navigation closes it
57
- * explicitly and `onContentProcessDidTerminate` catches OS-level
58
- * crashes, but the host may still re-announce UI_OPEN for an already
59
- * mounted WebView after app resume/dev respawn so the background can
60
- * push a fresh authoritative snapshot.
53
+ * The host mounts at most one miniapp UI WebView at a time. Its background
54
+ * JSContext runs separately on the phone and can remain alive after UI closure.
55
+ * UI closure unbinds the WebView and sends UI_CLOSE; on iOS the host also exits
56
+ * the UI when `onContentProcessDidTerminate` reports a content-process exit.
57
+ * The host may re-announce UI_OPEN after app resume or background dev respawn
58
+ * so the background can push a fresh snapshot. This router has no WebView
59
+ * heartbeat timeout.
61
60
  */
62
61
 
63
62
  export class MentraUIRouter {
@@ -85,9 +84,8 @@ export class MentraUIRouter {
85
84
  }
86
85
 
87
86
  /**
88
- * Called when the WebView unmounts (user navigated away or the host
89
- * tore it down on heartbeat timeout). Pushes UI_CLOSE to background
90
- * so handlers can flush state, then drops the binding.
87
+ * Called when the host tears down the WebView. Drops the binding and
88
+ * sends UI_CLOSE to the background so handlers can flush state.
91
89
  */
92
90
  unbindWebView(packageName: string): void {
93
91
  if (!this.bindings.has(packageName)) return
@@ -123,7 +121,6 @@ export class MentraUIRouter {
123
121
  * Recognised envelope types from the WebView shim:
124
122
  * - {type: "ready"} → fire UI_OPEN
125
123
  * - {type: "msg", seq, channel, payload} → fire UI_MESSAGE
126
- * - {type: "heartbeat", seq} → silently ack
127
124
  */
128
125
  routeFromWebView(packageName: string, rawJson: string): void {
129
126
  let env: {
@@ -317,12 +317,9 @@ class MiniappLauncher {
317
317
  /**
318
318
  * Re-spawn local miniapps that were running when the app was last killed.
319
319
  *
320
- * Cloud apps run on a remote server: the cloud persists which ones are
321
- * running and resurrects them when the phone reconnects, so a host-app
322
- * restart brings them back on its own. Local (phone-hosted) miniapps run in
323
- * the phone's own JS engine — a host-process kill tears down their JSContext
324
- * and there is no server to resurrect them, so without this they silently
325
- * stay stopped on the next launch even though the user left them running.
320
+ * Every miniapp runs as local JavaScript in the Mentra App on the phone.
321
+ * A host-process kill tears down its JSContext, so without this restoration
322
+ * it stays stopped on the next launch even though the user left it running.
326
323
  *
327
324
  * Each local miniapp persists its running flag to disk on start/stop
328
325
  * (`saveLocalAppRunningState`, via the applet's `onStart`/`onStop`). We read
@@ -331,9 +328,9 @@ class MiniappLauncher {
331
328
  * tray/switcher project it as running, exactly as a normal start would. The
332
329
  * WebView, if any, re-attaches lazily when the user opens the app.
333
330
  *
334
- * Idempotent and best-effort: skips already-spawned contexts, and clears the
335
- * persisted flag for any app whose bundle can no longer be resolved
336
- * (uninstalled, or dev server gone) so a dead entry doesn't retry every boot.
331
+ * Idempotent and best-effort: skips already-spawned contexts and clears the
332
+ * persisted running flag if ensureRunning fails (for example, bundle resolution,
333
+ * deployment policy, or context spawning) so it does not retry every boot.
337
334
  * Not compatibility-gated: a previously-running background app shouldn't be
338
335
  * dropped just because glasses are momentarily disconnected at boot — it
339
336
  * resumes when they reconnect, same as mid-session.
@@ -3,6 +3,24 @@ export interface MiniappPingLivenessDecision {
3
3
  unansweredPingRounds: number
4
4
  }
5
5
 
6
+ /**
7
+ * SoftAP join can spend a minute in native `prepareAgent` before the glasses
8
+ * hotspot even turns on. The JS bundle stays alive but may miss pongs while
9
+ * ACS is on the native thread. Unregistering for missed pings in that window
10
+ * cancels the join and leaves the UI stuck on the hotspot row.
11
+ */
12
+ export function shouldHoldMiniappPingLiveness(args: {
13
+ packageName: string
14
+ softapPackageName?: string | null
15
+ softapCancelled?: boolean
16
+ }): boolean {
17
+ return Boolean(
18
+ args.softapPackageName &&
19
+ args.softapPackageName === args.packageName &&
20
+ !args.softapCancelled,
21
+ )
22
+ }
23
+
6
24
  /**
7
25
  * Advance liveness by one scheduled ping round.
8
26
  *
@@ -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 fans each fix to the v2 cloud
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:
@@ -21,8 +21,7 @@ import localMiniappRuntime from "./LocalMiniappRuntime"
21
21
 
22
22
  export const LOCATION_TASK_NAME = "handleLocationUpdates"
23
23
 
24
- // Background location task — sends each fix to the v2 cloud (RestComms) + local
25
- // miniapps. (The v1 SocketComms leg was already removed/commented before the move.)
24
+ // Background location task — forwards the first fix from each non-empty batch.
26
25
  TaskManager.defineTask<{locations?: Location.LocationObject[]}>(LOCATION_TASK_NAME, async ({data, error}) => {
27
26
  if (error) {
28
27
  // OS-level failure (permission revoked, GPS unavailable, …) — log it so
@@ -36,11 +35,8 @@ TaskManager.defineTask<{locations?: Location.LocationObject[]}>(LOCATION_TASK_NA
36
35
  return
37
36
  }
38
37
  const first = locs[0]!
39
- // Cloud path (relayMessageToApps) never reaches __phone__, so local miniapps rely
40
- // on this direct push.
41
- // Local miniapps get the update directly (the cloud relay never reaches
42
- // __phone__). The Cloud V1 upload that used to run here was removed with the
43
- // V1 ripout (issue #3392); a V2 location channel is separate product work.
38
+ // Deliver directly to local miniapps. The Cloud V1 upload that used to run
39
+ // here was removed with the V1 ripout (issue #3392).
44
40
  localMiniappRuntime.forwardEvent("location_update", {
45
41
  lat: first.coords.latitude,
46
42
  lng: first.coords.longitude,
@@ -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 and from cloud-SDK app streams that flow through
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
@@ -231,9 +230,12 @@ export class PhoneStreamCoordinator {
231
230
  * A stream was torn down while the BLE link was down, so the glasses never
232
231
  * got `stopStream`. Sent on the next reconnect (if no new stream has claimed
233
232
  * the slot) so a publisher that outlived its input does not keep pushing
234
- * until its own watchdog fires.
233
+ * until its own watchdog fires. `generation` belongs to the publisher that
234
+ * queued it; a later SoftAP media hop must not inherit this stop.
235
235
  */
236
- private pendingBleStop: {streamId: string; hotspot?: boolean} | null = null
236
+ private pendingBleStop: {streamId: string; hotspot?: boolean; generation: number; discarded?: boolean} | null = null
237
+ /** Bumped each time an unmanaged/managed publisher claims the slot. */
238
+ private publisherGeneration = 0
237
239
  /**
238
240
  * Serializes state transitions (start, stop, teardown). Without it, a
239
241
  * second `start*` racing with the first can pass the `this.current === null`
@@ -364,6 +366,7 @@ export class PhoneStreamCoordinator {
364
366
  }
365
367
 
366
368
  const streamId = this.mintId("u")
369
+ ++this.publisherGeneration
367
370
  const entry: UnmanagedEntry = {
368
371
  kind: "unmanaged",
369
372
  streamId,
@@ -462,6 +465,7 @@ export class PhoneStreamCoordinator {
462
465
  // and joins instead of double-provisioning.
463
466
  const provision = await provisionManagedStream(opts.restreamDestinations)
464
467
  const streamId = this.mintId("m")
468
+ ++this.publisherGeneration
465
469
  let ingestUrl: string
466
470
  try {
467
471
  ingestUrl = pickIngestUrl(provision, opts.ingest)
@@ -514,7 +518,7 @@ export class PhoneStreamCoordinator {
514
518
  },
515
519
  () => this.linkSource.isConnected(),
516
520
  () => {
517
- this.pendingBleStop = {streamId, hotspot: true}
521
+ this.pendingBleStop = {streamId, hotspot: true, generation: this.publisherGeneration}
518
522
  this.attachLink()
519
523
  },
520
524
  )
@@ -616,6 +620,22 @@ export class PhoneStreamCoordinator {
616
620
  })
617
621
  }
618
622
 
623
+ /**
624
+ * Drop a deferred BLE `stopStream` that belonged to a publisher that is gone.
625
+ *
626
+ * SoftAP recovery destroys generation N and rebuilds N+1. If failSuspended already
627
+ * tore the publisher down after `glassesGraceMs`, `stop()` is a no-op but a pending
628
+ * stop would still flush into the new hop on reconnect. Call this from SoftAP
629
+ * `stopPublishing` so the deferred command dies with its generation.
630
+ */
631
+ discardPendingBleStop(): void {
632
+ const pending = this.pendingBleStop
633
+ if (!pending) return
634
+ pending.discarded = true
635
+ this.pendingBleStop = null
636
+ this.detachLinkIfIdle()
637
+ }
638
+
619
639
  /**
620
640
  * Called by MantleManager when a `stream_status` event arrives from glasses
621
641
  * and the registry says it's phone-owned.
@@ -760,9 +780,10 @@ export class PhoneStreamCoordinator {
760
780
 
761
781
  private async flushPendingBleStop(): Promise<void> {
762
782
  const pending = this.pendingBleStop
763
- if (!pending || this.current || !this.linkSource.isConnected()) return
783
+ if (!pending || pending.discarded || this.current || !this.linkSource.isConnected()) return
764
784
  console.info("[STREAM] BLE link back; sending deferred stopStream", pending)
765
785
  await BluetoothSdk.stopStream()
786
+ if (pending.discarded || this.pendingBleStop !== pending || this.current) return
766
787
  if (pending.hotspot) {
767
788
  const result = await BluetoothSdk.setHotspotState(false)
768
789
  if (result.state !== "disabled") throw new Error("Deferred hotspot shutdown was not confirmed")
@@ -1010,7 +1031,7 @@ export class PhoneStreamCoordinator {
1010
1031
  // lock for the native timeout). Defer it to the next reconnect instead.
1011
1032
  const linkUp = this.linkSource.isConnected()
1012
1033
  if (sendBleStop && !linkUp) {
1013
- this.pendingBleStop = {streamId: entry.streamId, hotspot: entry.kind === "managed" && !!entry.relay}
1034
+ this.pendingBleStop = {streamId: entry.streamId, hotspot: entry.kind === "managed" && !!entry.relay, generation: this.publisherGeneration}
1014
1035
  console.warn("[STREAM] BLE link down during teardown; stopStream deferred", {
1015
1036
  streamId: entry.streamId,
1016
1037
  reason,