@mentra/cloud-client 3.2.1-beta.502 → 3.2.1-beta.508

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mentra/cloud-client",
3
- "version": "3.2.1-beta.502",
3
+ "version": "3.2.1-beta.508",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "exports": {
@@ -12,7 +12,7 @@
12
12
  "test": "bun test"
13
13
  },
14
14
  "dependencies": {
15
- "@mentra/cloud-protocol": "3.2.1-beta.502",
15
+ "@mentra/cloud-protocol": "3.2.1-beta.508",
16
16
  "tweetnacl": "^1.0.3"
17
17
  },
18
18
  "devDependencies": {
@@ -78,6 +78,13 @@ export class UdpAudio {
78
78
  return this.session?.sessionTag ?? null;
79
79
  }
80
80
 
81
+ /** Refresh a dead UDP route without changing the session key, tag or sequence. */
82
+ resetSocket(): void {
83
+ if (!this.session) return;
84
+ this.session.socket.close();
85
+ this.session.socket = this.udpFactory();
86
+ }
87
+
81
88
  /**
82
89
  * Open the UDP socket and load the session's routing + encryption material
83
90
  * from `connection.ack.audio`.
@@ -162,6 +162,7 @@ export class Runtime implements RuntimeModule {
162
162
  private udpProbeTimer: unknown | null = null;
163
163
  private udpProbeStartedAt = 0;
164
164
  private lastUdpAckAt = 0;
165
+ private readonly pendingUdpProbes = new Map<string, number>();
165
166
 
166
167
  /**
167
168
  * Whether the inbound-message routing has been wired to the connection yet.
@@ -393,7 +394,9 @@ export class Runtime implements RuntimeModule {
393
394
  private configureAudio(ack: ConnectionAck): void {
394
395
  if (ack.audio) {
395
396
  this.audio.configure(ack.audio);
396
- this.updateStatus({ audioTransport: "udp" });
397
+ // Configured credentials do not prove DNS/connect or UDP delivery works.
398
+ // Use the already-open WS while probes establish the new UDP route.
399
+ this.updateStatus({ audioTransport: this.connection.isOpen ? "ws" : "none" });
397
400
  this.startUdpLiveness();
398
401
  return;
399
402
  }
@@ -536,8 +539,8 @@ export class Runtime implements RuntimeModule {
536
539
  this.lastUdpAckAt = 0;
537
540
  this.sendUdpProbe();
538
541
  this.udpProbeTimer = this.timers.setInterval(() => {
539
- this.sendUdpProbe();
540
542
  this.checkUdpLiveness();
543
+ this.sendUdpProbe();
541
544
  }, UDP_PROBE_INTERVAL_MS);
542
545
  }
543
546
 
@@ -548,12 +551,18 @@ export class Runtime implements RuntimeModule {
548
551
  }
549
552
  this.udpProbeStartedAt = 0;
550
553
  this.lastUdpAckAt = 0;
554
+ this.pendingUdpProbes.clear();
551
555
  }
552
556
 
553
557
  private sendUdpProbe(): void {
554
558
  const tag = this.audio.sessionTag;
555
559
  if (tag === null) return;
556
- const probeId = `${tag}-${Date.now()}-${Math.floor(Math.random() * 1_000_000)}`;
560
+ const now = Date.now();
561
+ for (const [id, sentAt] of this.pendingUdpProbes) {
562
+ if (now - sentAt >= UDP_LIVENESS_TIMEOUT_MS) this.pendingUdpProbes.delete(id);
563
+ }
564
+ const probeId = `${tag}-${now}-${Math.floor(Math.random() * 1_000_000)}`;
565
+ this.pendingUdpProbes.set(probeId, now);
557
566
  this.audio.sendProbe(probeId);
558
567
  }
559
568
 
@@ -562,6 +571,14 @@ export class Runtime implements RuntimeModule {
562
571
  const since = this.lastUdpAckAt || this.udpProbeStartedAt;
563
572
  if (since === 0 || Date.now() - since < UDP_LIVENESS_TIMEOUT_MS) return;
564
573
  this.updateStatus({ audioTransport: this.connection.isOpen ? "ws" : "none" });
574
+ // Connected UDP sockets retain their resolved peer and route. Recreate one
575
+ // after a missed liveness window so DNS/network changes can recover even
576
+ // when the WebSocket stayed open. Only this replacement's probes may restore
577
+ // UDP: an old socket's delayed ack does not establish the new route works.
578
+ this.pendingUdpProbes.clear();
579
+ this.audio.resetSocket();
580
+ this.lastUdpAckAt = 0;
581
+ this.udpProbeStartedAt = Date.now();
565
582
  }
566
583
 
567
584
  private handleUdpLivenessAck(payload: {
@@ -571,6 +588,9 @@ export class Runtime implements RuntimeModule {
571
588
  receivedAt: number;
572
589
  }): void {
573
590
  if (payload.sessionTag !== this.audio.sessionTag) return;
591
+ const sentAt = this.pendingUdpProbes.get(payload.probeId);
592
+ this.pendingUdpProbes.delete(payload.probeId);
593
+ if (sentAt === undefined || Date.now() - sentAt >= UDP_LIVENESS_TIMEOUT_MS) return;
574
594
  this.lastUdpAckAt = Date.now();
575
595
  if (this.status.status === "connected") {
576
596
  this.updateStatus({ audioTransport: "udp" });