@pollen-robotics/reachy-mini-sdk 1.8.1 → 1.8.2

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,19 +1,19 @@
1
1
  var $ = Object.defineProperty, O = (r, e, t) => e in r ? $(r, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : r[e] = t, C = (r, e, t) => (O(r, typeof e != "symbol" ? e + "" : e, t), t), D = "https://huggingface.co";
2
2
  async function A(r, e) {
3
- var t, i;
4
- const s = new x(r.url, r.status, (t = r.headers.get("X-Request-Id")) != null ? t : void 0);
5
- s.message = `Api error with status ${s.statusCode}`;
6
- const o = [`URL: ${s.url}`, s.requestId ? `Request ID: ${s.requestId}` : void 0].filter(Boolean).join(". ");
7
- if ((i = r.headers.get("Content-Type")) != null && i.startsWith("application/json")) {
3
+ var t, s;
4
+ const i = new x(r.url, r.status, (t = r.headers.get("X-Request-Id")) != null ? t : void 0);
5
+ i.message = `Api error with status ${i.statusCode}`;
6
+ const o = [`URL: ${i.url}`, i.requestId ? `Request ID: ${i.requestId}` : void 0].filter(Boolean).join(". ");
7
+ if ((s = r.headers.get("Content-Type")) != null && s.startsWith("application/json")) {
8
8
  const n = await r.json();
9
- s.message = n.error || n.message || s.message, s.data = n;
9
+ i.message = n.error || n.message || i.message, i.data = n;
10
10
  } else
11
- s.data = { message: await r.text() };
12
- throw s.message += `. ${o}`, s;
11
+ i.data = { message: await r.text() };
12
+ throw i.message += `. ${o}`, i;
13
13
  }
14
14
  var x = class extends Error {
15
- constructor(r, e, t, i) {
16
- super(i), C(this, "statusCode"), C(this, "url"), C(this, "requestId"), C(this, "data"), this.statusCode = e, this.requestId = t, this.url = r;
15
+ constructor(r, e, t, s) {
16
+ super(s), C(this, "statusCode"), C(this, "url"), C(this, "requestId"), C(this, "data"), this.statusCode = e, this.requestId = t, this.url = r;
17
17
  }
18
18
  };
19
19
  new Promise((r) => {
@@ -32,10 +32,10 @@ async function M(r) {
32
32
  var e, t;
33
33
  if (typeof window > "u")
34
34
  throw new Error("oauthHandleRedirect is only available in the browser");
35
- const i = new URLSearchParams(window.location.search), [s, o] = [i.get("error"), i.get("error_description")];
36
- if (s)
37
- throw new Error(`${s}: ${o}`);
38
- const n = i.get("code"), a = localStorage.getItem("huggingface.co:oauth:nonce");
35
+ const s = new URLSearchParams(window.location.search), [i, o] = [s.get("error"), s.get("error_description")];
36
+ if (i)
37
+ throw new Error(`${i}: ${o}`);
38
+ const n = s.get("code"), a = localStorage.getItem("huggingface.co:oauth:nonce");
39
39
  if (!n)
40
40
  throw new Error("Missing oauth code from query parameters in redirected URL");
41
41
  if (!a)
@@ -43,7 +43,7 @@ async function M(r) {
43
43
  const l = localStorage.getItem("huggingface.co:oauth:code_verifier");
44
44
  if (!l)
45
45
  throw new Error("Missing oauth code_verifier from localStorage");
46
- const c = i.get("state");
46
+ const c = s.get("state");
47
47
  if (!c)
48
48
  throw new Error("Missing oauth state from query parameters in redirected URL");
49
49
  let h;
@@ -54,14 +54,14 @@ async function M(r) {
54
54
  }
55
55
  if (h.nonce !== a)
56
56
  throw new Error("Invalid oauth state in redirected URL");
57
- const d = D, p = `${new URL(d).origin}/.well-known/openid-configuration`, f = await fetch(p, {
57
+ const d = D, m = `${new URL(d).origin}/.well-known/openid-configuration`, f = await fetch(m, {
58
58
  headers: {
59
59
  Accept: "application/json"
60
60
  }
61
61
  });
62
62
  if (!f.ok)
63
63
  throw await A(f);
64
- const m = await f.json(), v = await fetch(m.token_endpoint, {
64
+ const p = await f.json(), v = await fetch(p.token_endpoint, {
65
65
  method: "POST",
66
66
  headers: {
67
67
  "Content-Type": "application/x-www-form-urlencoded"
@@ -75,7 +75,7 @@ async function M(r) {
75
75
  });
76
76
  if (localStorage.removeItem("huggingface.co:oauth:code_verifier"), localStorage.removeItem("huggingface.co:oauth:nonce"), !v.ok)
77
77
  throw await A(v);
78
- const w = await v.json(), g = new Date(Date.now() + w.expires_in * 1e3), S = await fetch(m.userinfo_endpoint, {
78
+ const w = await v.json(), g = new Date(Date.now() + w.expires_in * 1e3), S = await fetch(p.userinfo_endpoint, {
79
79
  headers: {
80
80
  Authorization: `Bearer ${w.access_token}`
81
81
  }
@@ -121,7 +121,7 @@ async function V(r) {
121
121
  var e, t;
122
122
  if (typeof window > "u")
123
123
  throw new Error("oauthLogin is only available in the browser");
124
- const i = r?.hubUrl || D, s = `${new URL(i).origin}/.well-known/openid-configuration`, o = await fetch(s, {
124
+ const s = r?.hubUrl || D, i = `${new URL(s).origin}/.well-known/openid-configuration`, o = await fetch(i, {
125
125
  headers: {
126
126
  Accept: "application/json"
127
127
  }
@@ -134,14 +134,14 @@ async function V(r) {
134
134
  nonce: a,
135
135
  redirectUri: c,
136
136
  state: r?.state
137
- }), d = (t = (e = window?.huggingface) == null ? void 0 : e.variables) != null ? t : null, p = r?.clientId || d?.OAUTH_CLIENT_ID;
138
- if (!p)
137
+ }), d = (t = (e = window?.huggingface) == null ? void 0 : e.variables) != null ? t : null, m = r?.clientId || d?.OAUTH_CLIENT_ID;
138
+ if (!m)
139
139
  throw d ? new Error("Missing clientId, please add hf_oauth: true to the README.md's metadata in your static Space") : new Error("Missing clientId");
140
140
  const f = N(
141
141
  new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(l)))
142
142
  ).replace(/[+]/g, "-").replace(/[/]/g, "_").replace(/=/g, "");
143
143
  return `${n.authorization_endpoint}?${new URLSearchParams({
144
- client_id: p,
144
+ client_id: m,
145
145
  scope: r?.scopes || d?.OAUTH_SCOPES || "openid profile",
146
146
  response_type: "code",
147
147
  redirect_uri: c,
@@ -154,7 +154,7 @@ function R(r) {
154
154
  return r * Math.PI / 180;
155
155
  }
156
156
  function F(r, e, t) {
157
- const i = R(r), s = R(e), o = R(t), n = Math.cos(o), a = Math.sin(o), l = Math.cos(s), c = Math.sin(s), h = Math.cos(i), d = Math.sin(i);
157
+ const s = R(r), i = R(e), o = R(t), n = Math.cos(o), a = Math.sin(o), l = Math.cos(i), c = Math.sin(i), h = Math.cos(s), d = Math.sin(s);
158
158
  return [
159
159
  [n * l, n * c * d - a * h, n * c * h + a * d, 0],
160
160
  [a * l, a * c * d + n * h, a * c * h - n * d, 0],
@@ -173,9 +173,9 @@ function B() {
173
173
  }
174
174
  const t = e.get("hf_token");
175
175
  if (!t) return;
176
- const i = e.get("hf_username") || "user", s = e.get("hf_token_expires"), o = s && !Number.isNaN(new Date(s).getTime()) ? s : new Date(Date.now() + 365 * 24 * 60 * 60 * 1e3).toISOString();
176
+ const s = e.get("hf_username") || "user", i = e.get("hf_token_expires"), o = i && !Number.isNaN(new Date(i).getTime()) ? i : new Date(Date.now() + 365 * 24 * 60 * 60 * 1e3).toISOString();
177
177
  try {
178
- sessionStorage.setItem("hf_token", t), sessionStorage.setItem("hf_username", i), sessionStorage.setItem("hf_token_expires", o);
178
+ sessionStorage.setItem("hf_token", t), sessionStorage.setItem("hf_username", s), sessionStorage.setItem("hf_token_expires", o);
179
179
  } catch (l) {
180
180
  console.warn("[reachy-mini] could not persist pre-seeded HF credentials:", l);
181
181
  }
@@ -208,8 +208,8 @@ function q(r) {
208
208
  const e = r.split(`\r
209
209
  `);
210
210
  let t = !1;
211
- for (const i of e)
212
- if (i.startsWith("m=audio") ? t = !0 : i.startsWith("m=") && (t = !1), t && i === "a=sendrecv") return !0;
211
+ for (const s of e)
212
+ if (s.startsWith("m=audio") ? t = !0 : s.startsWith("m=") && (t = !1), t && s === "a=sendrecv") return !0;
213
213
  return !1;
214
214
  }
215
215
  const y = 12 * 1024, E = 1 * 1024 * 1024, W = 512 * 1024;
@@ -221,10 +221,10 @@ function U() {
221
221
  }
222
222
  function k(r) {
223
223
  let e = "";
224
- for (let i = 0; i < r.length; i += 32768)
224
+ for (let s = 0; s < r.length; s += 32768)
225
225
  e += String.fromCharCode.apply(
226
226
  null,
227
- Array.from(r.subarray(i, i + 32768))
227
+ Array.from(r.subarray(s, s + 32768))
228
228
  );
229
229
  return btoa(e);
230
230
  }
@@ -238,7 +238,7 @@ function L(r) {
238
238
  const e = Math.round(Number(r) || 0);
239
239
  return Math.max(0, Math.min(100, e));
240
240
  }
241
- const j = 3e3, P = 1e3, X = 6e4;
241
+ const P = 3e3, j = 1e3, X = 6e4;
242
242
  class Y extends EventTarget {
243
243
  // ─── Config ──────────────────────────────────────────────────────────
244
244
  _signalingUrl;
@@ -284,6 +284,7 @@ class Y extends EventTarget {
284
284
  _readAudioParameterResolve = null;
285
285
  // ─── Log subscribers ─────────────────────────────────────────────────
286
286
  _logSubscribers = /* @__PURE__ */ new Set();
287
+ _updateProgressSubscribers = /* @__PURE__ */ new Set();
287
288
  // ─── Broadcast waiters (playMove / playUploadedAudio) ────────────────
288
289
  _broadcastWaiters = [];
289
290
  // ─── Active upload ids for no-arg cancels ────────────────────────────
@@ -355,12 +356,12 @@ class Y extends EventTarget {
355
356
  * SDK is already `connected` still triggers the auto-start.
356
357
  */
357
358
  _maybeAutoStart() {
358
- if (!this._autoStartFromUrl || this._autoStartAttempted || !this._preselectedRobotId || this._state !== "connected" || !this._robots.find((i) => i.id === this._preselectedRobotId)) return;
359
+ if (!this._autoStartFromUrl || this._autoStartAttempted || !this._preselectedRobotId || this._state !== "connected" || !this._robots.find((s) => s.id === this._preselectedRobotId)) return;
359
360
  this._autoStartAttempted = !0;
360
361
  const t = this._preselectedRobotId;
361
362
  setTimeout(() => {
362
- this._state === "connected" && this.startSession(t).catch((i) => {
363
- console.warn("[reachy-mini] autoStartFromUrl: startSession rejected:", i);
363
+ this._state === "connected" && this.startSession(t).catch((s) => {
364
+ console.warn("[reachy-mini] autoStartFromUrl: startSession rejected:", s);
364
365
  });
365
366
  }, 0);
366
367
  }
@@ -374,8 +375,8 @@ class Y extends EventTarget {
374
375
  "hf_token_expires",
375
376
  typeof this._tokenExpires == "string" ? this._tokenExpires : this._tokenExpires.toISOString()
376
377
  ), !0;
377
- const t = sessionStorage.getItem("hf_token"), i = sessionStorage.getItem("hf_username"), s = sessionStorage.getItem("hf_token_expires");
378
- return t && i && s && new Date(s) > /* @__PURE__ */ new Date() ? (this._token = t, this._username = i, this._tokenExpires = s, !0) : !1;
378
+ const t = sessionStorage.getItem("hf_token"), s = sessionStorage.getItem("hf_username"), i = sessionStorage.getItem("hf_token_expires");
379
+ return t && s && i && new Date(i) > /* @__PURE__ */ new Date() ? (this._token = t, this._username = s, this._tokenExpires = i, !0) : !1;
379
380
  } catch (e) {
380
381
  return console.error("Auth error:", e), !1;
381
382
  }
@@ -401,12 +402,12 @@ class Y extends EventTarget {
401
402
  headers: { Authorization: `Bearer ${this._token}` }
402
403
  }
403
404
  );
404
- } catch (i) {
405
- throw this._sseAbortController = null, i;
405
+ } catch (s) {
406
+ throw this._sseAbortController = null, s;
406
407
  }
407
408
  if (!t.ok)
408
409
  throw this._sseAbortController = null, new Error(`HTTP ${t.status}`);
409
- return new Promise((i, s) => {
410
+ return new Promise((s, i) => {
410
411
  let o = !1;
411
412
  const n = t.body.getReader(), a = new TextDecoder();
412
413
  let l = "";
@@ -416,36 +417,36 @@ class Y extends EventTarget {
416
417
  const { done: h, value: d } = await n.read();
417
418
  if (h) break;
418
419
  l += a.decode(d, { stream: !0 });
419
- const p = l.split(`
420
+ const m = l.split(`
420
421
  `);
421
- l = p.pop() ?? "";
422
- for (const f of p)
422
+ l = m.pop() ?? "";
423
+ for (const f of m)
423
424
  if (f.startsWith("data:"))
424
425
  try {
425
- const m = JSON.parse(f.slice(5).trim());
426
- !o && m.type === "welcome" && m.peerId && (o = !0, this._state = "connected", await this._sendToServer({
426
+ const p = JSON.parse(f.slice(5).trim());
427
+ !o && p.type === "welcome" && p.peerId && (o = !0, this._state = "connected", await this._sendToServer({
427
428
  type: "setPeerStatus",
428
429
  roles: ["listener"],
429
430
  meta: { name: this._appName }
430
- }), this._emit("connected", { peerId: m.peerId }), i()), this._handleSignalingMessage(m);
431
+ }), this._emit("connected", { peerId: p.peerId }), s()), this._handleSignalingMessage(p);
431
432
  } catch {
432
433
  }
433
434
  }
434
435
  } catch (h) {
435
436
  if (h.name !== "AbortError" && this._emit("error", { source: "signaling", error: h }), !o) {
436
- s(h);
437
+ i(h);
437
438
  return;
438
439
  }
439
440
  }
440
- this._state !== "disconnected" && (this._state = "disconnected", this._emit("disconnected", { reason: "SSE closed" })), o || s(new Error("Connection closed before welcome"));
441
+ this._state !== "disconnected" && (this._state = "disconnected", this._emit("disconnected", { reason: "SSE closed" })), o || i(new Error("Connection closed before welcome"));
441
442
  })();
442
443
  });
443
444
  }
444
445
  async autoConnect(e = {}) {
445
446
  const {
446
447
  token: t,
447
- pickRobot: i,
448
- autoPickIfSingle: s = !0,
448
+ pickRobot: s,
449
+ autoPickIfSingle: i = !0,
449
450
  filterBusy: o = !0,
450
451
  wakeOnConnect: n = !0
451
452
  } = e;
@@ -478,12 +479,12 @@ class Y extends EventTarget {
478
479
  const h = await this._fetchOwnedRobots({ filterBusy: o });
479
480
  if (h.length === 0)
480
481
  throw new Error("No reachable robots");
481
- if (s && h.length === 1 && !h[0].busy)
482
+ if (i && h.length === 1 && !h[0].busy)
482
483
  l = h[0].id, c = h[0].name;
483
- else if (i) {
484
- const d = await i(h);
484
+ else if (s) {
485
+ const d = await s(h);
485
486
  if (!d) throw new Error("Robot selection cancelled");
486
- l = d, c = h.find((p) => p.id === d)?.name ?? null;
487
+ l = d, c = h.find((m) => m.id === d)?.name ?? null;
487
488
  } else
488
489
  throw new Error(
489
490
  "Multiple robots available — pass a pickRobot callback to autoConnect()"
@@ -506,11 +507,11 @@ class Y extends EventTarget {
506
507
  headers: { Authorization: `Bearer ${this._token}` }
507
508
  });
508
509
  if (!t.ok) throw new Error(`HTTP ${t.status}`);
509
- const i = await t.json(), s = /* @__PURE__ */ new Map();
510
- for (const o of i.robots || []) {
510
+ const s = await t.json(), i = /* @__PURE__ */ new Map();
511
+ for (const o of s.robots || []) {
511
512
  if (e && o.busy) continue;
512
513
  const n = o.meta?.install_id ?? o.meta?.hardware_id ?? o.peerId;
513
- s.set(n, {
514
+ i.set(n, {
514
515
  id: o.peerId,
515
516
  name: o.robotName ?? o.meta?.name ?? null,
516
517
  busy: !!o.busy,
@@ -519,26 +520,26 @@ class Y extends EventTarget {
519
520
  lastSeenAgeSeconds: o.last_seen_age_seconds ?? null
520
521
  });
521
522
  }
522
- return Array.from(s.values()).sort(
523
+ return Array.from(i.values()).sort(
523
524
  (o, n) => (o.lastSeenAgeSeconds ?? 1 / 0) - (n.lastSeenAgeSeconds ?? 1 / 0)
524
525
  );
525
526
  } catch (t) {
526
- return console.warn("[reachy-mini] /api/robot-status unavailable, using SSE list:", t), (this._robots || []).map((i) => ({
527
- id: i.id,
528
- name: i.meta?.name ?? null,
527
+ return console.warn("[reachy-mini] /api/robot-status unavailable, using SSE list:", t), (this._robots || []).map((s) => ({
528
+ id: s.id,
529
+ name: s.meta?.name ?? null,
529
530
  busy: !1,
530
531
  activeApp: null,
531
- meta: i.meta ?? {},
532
+ meta: s.meta ?? {},
532
533
  lastSeenAgeSeconds: null
533
534
  }));
534
535
  }
535
536
  }
536
537
  _waitForRobotInList(e, t) {
537
- return this._robots?.find((i) => i.id === e) ? Promise.resolve() : new Promise((i, s) => {
538
+ return this._robots?.find((s) => s.id === e) ? Promise.resolve() : new Promise((s, i) => {
538
539
  const o = () => {
539
- this._robots?.find((a) => a.id === e) && (this.removeEventListener("robotsChanged", o), clearTimeout(n), i());
540
+ this._robots?.find((a) => a.id === e) && (this.removeEventListener("robotsChanged", o), clearTimeout(n), s());
540
541
  }, n = setTimeout(() => {
541
- this.removeEventListener("robotsChanged", o), s(new Error(`Timeout waiting for robot ${e} in list`));
542
+ this.removeEventListener("robotsChanged", o), i(new Error(`Timeout waiting for robot ${e} in list`));
542
543
  }, t);
543
544
  this.addEventListener("robotsChanged", o);
544
545
  });
@@ -547,9 +548,9 @@ class Y extends EventTarget {
547
548
  if (this._state !== "connected") throw new Error("Not connected");
548
549
  this._selectedRobotId = e, this._iceConnected = !1, this._dcOpen = !1, this._micSupported = !1, this._pendingRemoteIce = [];
549
550
  try {
550
- const t = window, i = t.AudioContext ?? t.webkitAudioContext;
551
- if (!i) throw new Error("AudioContext not supported");
552
- const s = new i(), o = s.createMediaStreamDestination(), n = s.createOscillator(), a = s.createGain();
551
+ const t = window, s = t.AudioContext ?? t.webkitAudioContext;
552
+ if (!s) throw new Error("AudioContext not supported");
553
+ const i = new s(), o = i.createMediaStreamDestination(), n = i.createOscillator(), a = i.createGain();
553
554
  a.gain.value = 0, n.connect(a).connect(o), n.start();
554
555
  const l = o.stream;
555
556
  l.getAudioTracks().forEach((c) => {
@@ -560,56 +561,56 @@ class Y extends EventTarget {
560
561
  }
561
562
  return this._pc = new RTCPeerConnection({
562
563
  iceServers: [{ urls: "stun:stun.l.google.com:19302" }]
563
- }), this._installNetworkListeners(), new Promise((t, i) => {
564
- this._sessionResolve = t, this._sessionReject = i, this._pc.ontrack = (s) => {
565
- if (s.track.kind === "video") {
564
+ }), this._installNetworkListeners(), new Promise((t, s) => {
565
+ this._sessionResolve = t, this._sessionReject = s, this._pc.ontrack = (i) => {
566
+ if (i.track.kind === "video") {
566
567
  const o = this._videoJitterBufferTargetMs;
567
568
  try {
568
- s.receiver.jitterBufferTarget = o;
569
+ i.receiver.jitterBufferTarget = o;
569
570
  } catch {
570
571
  }
571
572
  try {
572
- s.receiver.playoutDelayHint = o / 1e3;
573
+ i.receiver.playoutDelayHint = o / 1e3;
573
574
  } catch {
574
575
  }
575
- this._emit("videoTrack", { track: s.track, stream: s.streams[0] });
576
+ this._emit("videoTrack", { track: i.track, stream: i.streams[0] });
576
577
  }
577
- }, this._pc.onicecandidate = async (s) => {
578
- s.candidate && this._sessionId && await this._sendToServer({
578
+ }, this._pc.onicecandidate = async (i) => {
579
+ i.candidate && this._sessionId && await this._sendToServer({
579
580
  type: "peer",
580
581
  sessionId: this._sessionId,
581
582
  ice: {
582
- candidate: s.candidate.candidate,
583
- sdpMLineIndex: s.candidate.sdpMLineIndex,
584
- sdpMid: s.candidate.sdpMid
583
+ candidate: i.candidate.candidate,
584
+ sdpMLineIndex: i.candidate.sdpMLineIndex,
585
+ sdpMid: i.candidate.sdpMid
585
586
  }
586
587
  });
587
588
  }, this._pc.oniceconnectionstatechange = () => {
588
- const s = this._pc?.iceConnectionState;
589
- if (s) {
590
- if (this._emit("iceStateChange", { state: s }), s === "connected" || s === "completed") {
589
+ const i = this._pc?.iceConnectionState;
590
+ if (i) {
591
+ if (this._emit("iceStateChange", { state: i }), i === "connected" || i === "completed") {
591
592
  this._clearIceGrace(), this._iceConnected = !0, this._checkSessionReady();
592
593
  return;
593
594
  }
594
- if (s === "disconnected") {
595
- typeof document < "u" && document.hidden ? this._armIceGraceOnVisibility() : this._scheduleIceGrace(j, "disconnected");
595
+ if (i === "disconnected") {
596
+ typeof document < "u" && document.hidden ? this._armIceGraceOnVisibility() : this._scheduleIceGrace(P, "disconnected");
596
597
  return;
597
598
  }
598
- if (s === "failed") {
599
- this._scheduleIceGrace(P, "failed");
599
+ if (i === "failed") {
600
+ this._scheduleIceGrace(j, "failed");
600
601
  return;
601
602
  }
602
603
  }
603
- }, this._pc.ondatachannel = (s) => {
604
- this._dc = s.channel, this._dc.onopen = () => {
604
+ }, this._pc.ondatachannel = (i) => {
605
+ this._dc = i.channel, this._dc.onopen = () => {
605
606
  this._dcOpen = !0, this._checkSessionReady();
606
607
  }, this._dc.onmessage = (o) => this._handleRobotMessage(JSON.parse(o.data));
607
- }, this._sendToServer({ type: "startSession", peerId: e }).then((s) => {
608
- if (s?.type === "sessionRejected") {
609
- this._failSessionRejected(s);
608
+ }, this._sendToServer({ type: "startSession", peerId: e }).then((i) => {
609
+ if (i?.type === "sessionRejected") {
610
+ this._failSessionRejected(i);
610
611
  return;
611
612
  }
612
- s?.sessionId && (this._sessionId = s.sessionId);
613
+ i?.sessionId && (this._sessionId = i.sessionId);
613
614
  });
614
615
  });
615
616
  }
@@ -617,16 +618,16 @@ class Y extends EventTarget {
617
618
  const t = new Error(
618
619
  e.reason === "robot_busy" ? `Robot is busy: "${e.activeApp || "another app"}" is already connected` : `Session rejected: ${e.reason || "unknown reason"}`
619
620
  );
620
- if (t.reason = e.reason ?? null, t.activeApp = e.activeApp ?? null, this._clearIceGrace(), this._uninstallNetworkListeners(), this._pc && (this._pc.close(), this._pc = null), this._micStream && (this._micStream.getTracks().forEach((i) => i.stop()), this._micStream = null), this._iceConnected = !1, this._dcOpen = !1, this._micMuted = !0, this._micSupported = !1, this._emit("sessionRejected", { reason: e.reason, activeApp: e.activeApp }), this._sessionReject) {
621
- const i = this._sessionReject;
622
- this._sessionResolve = null, this._sessionReject = null, i(t);
621
+ if (t.reason = e.reason ?? null, t.activeApp = e.activeApp ?? null, this._clearIceGrace(), this._uninstallNetworkListeners(), this._pc && (this._pc.close(), this._pc = null), this._micStream && (this._micStream.getTracks().forEach((s) => s.stop()), this._micStream = null), this._iceConnected = !1, this._dcOpen = !1, this._micMuted = !0, this._micSupported = !1, this._emit("sessionRejected", { reason: e.reason, activeApp: e.activeApp }), this._sessionReject) {
622
+ const s = this._sessionReject;
623
+ this._sessionResolve = null, this._sessionReject = null, s(t);
623
624
  }
624
625
  }
625
626
  async stopSession() {
626
- this._versionResolve && (this._versionResolve(null), this._versionResolve = null), this._hardwareIdResolve && (this._hardwareIdResolve(null), this._hardwareIdResolve = null), this._volumeResolve && (this._volumeResolve(null), this._volumeResolve = null), this._micVolumeResolve && (this._micVolumeResolve(null), this._micVolumeResolve = null), this._applyAudioConfigResolve && (this._applyAudioConfigResolve(!1), this._applyAudioConfigResolve = null), this._readAudioParameterResolve && (this._readAudioParameterResolve(null), this._readAudioParameterResolve = null), this._logSubscribers.clear(), this._rejectPendingMotionCompletions(new Error("Session stopped")), this._clearIceGrace(), this._uninstallNetworkListeners(), this._sessionReject && (this._sessionReject(new Error("Session stopped")), this._sessionResolve = null, this._sessionReject = null), this._stateRefreshInterval && (clearInterval(this._stateRefreshInterval), this._stateRefreshInterval = null), this._latencyMonitorId && (clearInterval(this._latencyMonitorId), this._latencyMonitorId = null), this._sessionId && await this._sendToServer({ type: "endSession", sessionId: this._sessionId }), this._micStream && (this._micStream.getTracks().forEach((t) => t.stop()), this._micStream = null), this._micMuted = !0, this._micSupported = !1, this._pc && (this._pc.close(), this._pc = null), this._dc && (this._dc.close(), this._dc = null), this._sessionId = null, this._iceConnected = !1, this._dcOpen = !1, this._state === "streaming" && (this._state = "connected", this._emit("sessionStopped", { reason: "user" }));
627
+ this._versionResolve && (this._versionResolve(null), this._versionResolve = null), this._hardwareIdResolve && (this._hardwareIdResolve(null), this._hardwareIdResolve = null), this._volumeResolve && (this._volumeResolve(null), this._volumeResolve = null), this._micVolumeResolve && (this._micVolumeResolve(null), this._micVolumeResolve = null), this._applyAudioConfigResolve && (this._applyAudioConfigResolve(!1), this._applyAudioConfigResolve = null), this._readAudioParameterResolve && (this._readAudioParameterResolve(null), this._readAudioParameterResolve = null), this._logSubscribers.clear(), this._updateProgressSubscribers.clear(), this._rejectPendingMotionCompletions(new Error("Session stopped")), this._clearIceGrace(), this._uninstallNetworkListeners(), this._sessionReject && (this._sessionReject(new Error("Session stopped")), this._sessionResolve = null, this._sessionReject = null), this._stateRefreshInterval && (clearInterval(this._stateRefreshInterval), this._stateRefreshInterval = null), this._latencyMonitorId && (clearInterval(this._latencyMonitorId), this._latencyMonitorId = null), this._sessionId && await this._sendToServer({ type: "endSession", sessionId: this._sessionId }), this._micStream && (this._micStream.getTracks().forEach((t) => t.stop()), this._micStream = null), this._micMuted = !0, this._micSupported = !1, this._pc && (this._pc.close(), this._pc = null), this._dc && (this._dc.close(), this._dc = null), this._sessionId = null, this._iceConnected = !1, this._dcOpen = !1, this._state === "streaming" && (this._state = "connected", this._emit("sessionStopped", { reason: "user" }));
627
628
  }
628
629
  disconnect() {
629
- this._sseAbortController && (this._sseAbortController.abort(), this._sseAbortController = null), this._versionResolve && (this._versionResolve(null), this._versionResolve = null), this._hardwareIdResolve && (this._hardwareIdResolve(null), this._hardwareIdResolve = null), this._volumeResolve && (this._volumeResolve(null), this._volumeResolve = null), this._micVolumeResolve && (this._micVolumeResolve(null), this._micVolumeResolve = null), this._applyAudioConfigResolve && (this._applyAudioConfigResolve(!1), this._applyAudioConfigResolve = null), this._readAudioParameterResolve && (this._readAudioParameterResolve(null), this._readAudioParameterResolve = null), this._logSubscribers.clear(), this._rejectPendingMotionCompletions(new Error("Disconnected")), this._clearIceGrace(), this._uninstallNetworkListeners(), this._sessionReject && (this._sessionReject(new Error("Disconnected")), this._sessionResolve = null, this._sessionReject = null), this._stateRefreshInterval && (clearInterval(this._stateRefreshInterval), this._stateRefreshInterval = null), this._latencyMonitorId && (clearInterval(this._latencyMonitorId), this._latencyMonitorId = null), this._sessionId && this._token && this._sendToServer({ type: "endSession", sessionId: this._sessionId }), this._micStream && (this._micStream.getTracks().forEach((e) => e.stop()), this._micStream = null), this._pc && (this._pc.close(), this._pc = null), this._dc && (this._dc.close(), this._dc = null), this._sessionId = null, this._micMuted = !0, this._micSupported = !1, this._iceConnected = !1, this._dcOpen = !1, this._robots = [], this._state = "disconnected", this._emit("disconnected", { reason: "user" });
630
+ this._sseAbortController && (this._sseAbortController.abort(), this._sseAbortController = null), this._versionResolve && (this._versionResolve(null), this._versionResolve = null), this._hardwareIdResolve && (this._hardwareIdResolve(null), this._hardwareIdResolve = null), this._volumeResolve && (this._volumeResolve(null), this._volumeResolve = null), this._micVolumeResolve && (this._micVolumeResolve(null), this._micVolumeResolve = null), this._applyAudioConfigResolve && (this._applyAudioConfigResolve(!1), this._applyAudioConfigResolve = null), this._readAudioParameterResolve && (this._readAudioParameterResolve(null), this._readAudioParameterResolve = null), this._logSubscribers.clear(), this._updateProgressSubscribers.clear(), this._rejectPendingMotionCompletions(new Error("Disconnected")), this._clearIceGrace(), this._uninstallNetworkListeners(), this._sessionReject && (this._sessionReject(new Error("Disconnected")), this._sessionResolve = null, this._sessionReject = null), this._stateRefreshInterval && (clearInterval(this._stateRefreshInterval), this._stateRefreshInterval = null), this._latencyMonitorId && (clearInterval(this._latencyMonitorId), this._latencyMonitorId = null), this._sessionId && this._token && this._sendToServer({ type: "endSession", sessionId: this._sessionId }), this._micStream && (this._micStream.getTracks().forEach((e) => e.stop()), this._micStream = null), this._pc && (this._pc.close(), this._pc = null), this._dc && (this._dc.close(), this._dc = null), this._sessionId = null, this._micMuted = !0, this._micSupported = !1, this._iceConnected = !1, this._dcOpen = !1, this._robots = [], this._state = "disconnected", this._emit("disconnected", { reason: "user" });
630
631
  }
631
632
  // ─── Resilience: ICE-blip debounce + network awareness ───────────────
632
633
  //
@@ -660,18 +661,18 @@ class Y extends EventTarget {
660
661
  }
661
662
  this._iceGraceReason = t, this._iceGraceTimer = setTimeout(() => {
662
663
  this._iceGraceTimer = null;
663
- const i = this._iceGraceReason;
664
+ const s = this._iceGraceReason;
664
665
  this._iceGraceReason = null;
665
- const s = this._pc?.iceConnectionState;
666
- if (!(s === "connected" || s === "completed")) {
667
- if (i === "disconnected" && s === "disconnected") {
666
+ const i = this._pc?.iceConnectionState;
667
+ if (!(i === "connected" || i === "completed")) {
668
+ if (s === "disconnected" && i === "disconnected") {
668
669
  this._emit("error", {
669
670
  source: "webrtc",
670
671
  error: new Error(`ICE stuck in 'disconnected' for > ${e}ms`)
671
672
  });
672
673
  return;
673
674
  }
674
- if (i === "failed" || s === "failed") {
675
+ if (s === "failed" || i === "failed") {
675
676
  const o = new Error("ICE connection failed");
676
677
  this._sessionReject && (this._sessionReject(o), this._sessionResolve = null, this._sessionReject = null), this._emit("error", { source: "webrtc", error: o });
677
678
  }
@@ -689,20 +690,20 @@ class Y extends EventTarget {
689
690
  if (this._pendingVisibilityHandler) return;
690
691
  const e = Date.now(), t = () => {
691
692
  if (typeof document < "u" && document.hidden || (document.removeEventListener("visibilitychange", t), this._pendingVisibilityHandler = null, !this._pc)) return;
692
- const i = this._pc.iceConnectionState;
693
- if (!(i === "connected" || i === "completed")) {
693
+ const s = this._pc.iceConnectionState;
694
+ if (!(s === "connected" || s === "completed")) {
694
695
  if (Date.now() - e > X) {
695
- const s = new Error(
696
+ const i = new Error(
696
697
  "Session expired while tab was backgrounded"
697
698
  );
698
- this._sessionReject && (this._sessionReject(s), this._sessionResolve = null, this._sessionReject = null), this._emit("error", { source: "webrtc", error: s });
699
+ this._sessionReject && (this._sessionReject(i), this._sessionResolve = null, this._sessionReject = null), this._emit("error", { source: "webrtc", error: i });
699
700
  return;
700
701
  }
701
- if (i === "failed") {
702
- this._scheduleIceGrace(P, "failed");
702
+ if (s === "failed") {
703
+ this._scheduleIceGrace(j, "failed");
703
704
  return;
704
705
  }
705
- this._scheduleIceGrace(j, "disconnected");
706
+ this._scheduleIceGrace(P, "disconnected");
706
707
  }
707
708
  };
708
709
  document.addEventListener("visibilitychange", t), this._pendingVisibilityHandler = t;
@@ -731,15 +732,15 @@ class Y extends EventTarget {
731
732
  if (this._onlineHandler || typeof window > "u") return;
732
733
  const e = () => this._emit("networkOnline", {}), t = () => this._emit("networkOffline", {});
733
734
  window.addEventListener("online", e), window.addEventListener("offline", t), this._onlineHandler = e, this._offlineHandler = t;
734
- const i = navigator.connection;
735
- if (i && typeof i.addEventListener == "function") {
736
- const s = () => this._emit("networkChange", {
737
- effectiveType: i.effectiveType,
738
- downlink: i.downlink,
739
- rtt: i.rtt,
740
- saveData: i.saveData
735
+ const s = navigator.connection;
736
+ if (s && typeof s.addEventListener == "function") {
737
+ const i = () => this._emit("networkChange", {
738
+ effectiveType: s.effectiveType,
739
+ downlink: s.downlink,
740
+ rtt: s.rtt,
741
+ saveData: s.saveData
741
742
  });
742
- i.addEventListener("change", s), this._connectionChangeHandler = s;
743
+ s.addEventListener("change", i), this._connectionChangeHandler = i;
743
744
  }
744
745
  }
745
746
  /** Counterpart to `_installNetworkListeners`. */
@@ -792,32 +793,32 @@ class Y extends EventTarget {
792
793
  * @returns `true` if the command was queued on the data channel,
793
794
  * `false` if the channel is not open.
794
795
  */
795
- setTarget({ head: e, antennas: t, body_yaw: i } = {}) {
796
- const s = { type: "set_full_target" };
796
+ setTarget({ head: e, antennas: t, body_yaw: s } = {}) {
797
+ const i = { type: "set_full_target" };
797
798
  if (e !== void 0) {
798
799
  if (!Array.isArray(e) || e.length !== 16 || !e.every((o) => Number.isFinite(o)))
799
800
  throw new TypeError(
800
801
  `setTarget: head must be a 16-element flat row-major 4×4 matrix of finite numbers; got ${Array.isArray(e) ? `Array(${e.length})` : typeof e}`
801
802
  );
802
- s.head = e;
803
+ i.head = e;
803
804
  }
804
805
  if (t !== void 0) {
805
806
  if (!Array.isArray(t) || t.length !== 2 || !t.every((o) => Number.isFinite(o)))
806
807
  throw new TypeError(
807
808
  `setTarget: antennas must be [rightRad, leftRad] (2 finite numbers); got ${Array.isArray(t) ? `Array(${t.length})` : typeof t}`
808
809
  );
809
- s.antennas = t;
810
+ i.antennas = t;
810
811
  }
811
- if (i !== void 0) {
812
- if (!Number.isFinite(i))
812
+ if (s !== void 0) {
813
+ if (!Number.isFinite(s))
813
814
  throw new TypeError(
814
- `setTarget: body_yaw must be a finite number (radians); got ${i}`
815
+ `setTarget: body_yaw must be a finite number (radians); got ${s}`
815
816
  );
816
- s.body_yaw = i;
817
+ i.body_yaw = s;
817
818
  }
818
- return this._sendCommand(s);
819
+ return this._sendCommand(i);
819
820
  }
820
- gotoTarget({ head: e, antennas: t, body_yaw: i, duration: s }) {
821
+ gotoTarget({ head: e, antennas: t, body_yaw: s, duration: i }) {
821
822
  const o = { type: "goto_target" };
822
823
  if (e !== void 0) {
823
824
  if (!Array.isArray(e) || e.length !== 16 || !e.every((n) => Number.isFinite(n)))
@@ -833,21 +834,21 @@ class Y extends EventTarget {
833
834
  );
834
835
  o.antennas = t;
835
836
  }
836
- if (i !== void 0) {
837
- if (!Number.isFinite(i))
837
+ if (s !== void 0) {
838
+ if (!Number.isFinite(s))
838
839
  throw new TypeError(
839
- `gotoTarget: body_yaw must be a finite number (radians); got ${i}`
840
+ `gotoTarget: body_yaw must be a finite number (radians); got ${s}`
840
841
  );
841
- o.body_yaw = i;
842
+ o.body_yaw = s;
842
843
  }
843
- if (!Number.isFinite(s) || s <= 0)
844
+ if (!Number.isFinite(i) || i <= 0)
844
845
  throw new TypeError(
845
- `gotoTarget: duration must be a positive finite number (seconds); got ${s}`
846
+ `gotoTarget: duration must be a positive finite number (seconds); got ${i}`
846
847
  );
847
- return o.duration = s, this._sendCommand(o);
848
+ return o.duration = i, this._sendCommand(o);
848
849
  }
849
- setHeadRpyDeg(e, t, i) {
850
- return this.setTarget({ head: F(e, t, i).flat() });
850
+ setHeadRpyDeg(e, t, s) {
851
+ return this.setTarget({ head: F(e, t, s).flat() });
851
852
  }
852
853
  setAntennasDeg(e, t) {
853
854
  return this.setTarget({ antennas: [R(e), R(t)] });
@@ -861,6 +862,23 @@ class Y extends EventTarget {
861
862
  clearIncomingAudio() {
862
863
  return this._sendCommand({ type: "clear_incoming_audio" });
863
864
  }
865
+ /**
866
+ * Trigger a PyPI update of the daemon over the data channel. Remote
867
+ * counterpart of `POST /update/start`. The daemon acks then restarts
868
+ * itself once the install finishes, which tears this session down -
869
+ * the caller is expected to reconnect afterwards.
870
+ *
871
+ * Pass `onProgress` to receive `update_progress` events (one per log
872
+ * line of the update job). A *successful* update restarts the daemon
873
+ * before a `done` event can arrive, so treat the session teardown +
874
+ * a successful reconnect as the success signal; `onProgress` will fire
875
+ * with `status: 'failed'` if the install errors before the restart.
876
+ *
877
+ * Returns `false` if the data channel isn't open.
878
+ */
879
+ startDaemonUpdate({ preRelease: e = !1, onProgress: t } = {}) {
880
+ return t && this._updateProgressSubscribers.add(t), this._sendCommand({ type: "start_update", pre_release: e });
881
+ }
864
882
  setMotorMode(e) {
865
883
  return this._sendCommand({ type: "set_motor_mode", mode: e });
866
884
  }
@@ -874,13 +892,13 @@ class Y extends EventTarget {
874
892
  return this._sendCommandAwaitCompletion("goto_sleep", e);
875
893
  }
876
894
  _sendCommandAwaitCompletion(e, t) {
877
- return this._sendCommand({ type: e }) ? new Promise((i, s) => {
895
+ return this._sendCommand({ type: e }) ? new Promise((s, i) => {
878
896
  const o = {
879
- resolve: i,
880
- reject: s,
897
+ resolve: s,
898
+ reject: i,
881
899
  timer: setTimeout(() => {
882
900
  const n = this._pendingMotionCompletions[e], a = n.indexOf(o);
883
- a !== -1 && n.splice(a, 1), s(new Error(`${e} timed out after ${t}ms`));
901
+ a !== -1 && n.splice(a, 1), i(new Error(`${e} timed out after ${t}ms`));
884
902
  }, t)
885
903
  };
886
904
  this._pendingMotionCompletions[e].push(o);
@@ -888,10 +906,10 @@ class Y extends EventTarget {
888
906
  }
889
907
  _rejectPendingMotionCompletions(e) {
890
908
  for (const t of Object.keys(this._pendingMotionCompletions)) {
891
- const i = this._pendingMotionCompletions[t];
892
- for (; i.length; ) {
893
- const s = i.shift();
894
- clearTimeout(s.timer), s.reject(e);
909
+ const s = this._pendingMotionCompletions[t];
910
+ for (; s.length; ) {
911
+ const i = s.shift();
912
+ clearTimeout(i.timer), i.reject(e);
895
913
  }
896
914
  }
897
915
  }
@@ -901,10 +919,10 @@ class Y extends EventTarget {
901
919
  }
902
920
  async ensureAwake(e = 1e3) {
903
921
  return this._robotState?.motor_mode === void 0 && await new Promise((t) => {
904
- const i = () => {
905
- this.removeEventListener("state", i), clearTimeout(s), t();
906
- }, s = setTimeout(i, e);
907
- this.addEventListener("state", i), this.requestState();
922
+ const s = () => {
923
+ this.removeEventListener("state", s), clearTimeout(i), t();
924
+ }, i = setTimeout(s, e);
925
+ this.addEventListener("state", s), this.requestState();
908
926
  }), this.isAwake() || this.wakeUp().catch(() => {
909
927
  }), !0;
910
928
  }
@@ -965,11 +983,11 @@ class Y extends EventTarget {
965
983
  applyAudioConfig(e, { verify: t = !0 } = {}) {
966
984
  return this._slotRoundtrip(
967
985
  () => this._applyAudioConfigResolve,
968
- (i) => {
969
- this._applyAudioConfigResolve = i;
986
+ (s) => {
987
+ this._applyAudioConfigResolve = s;
970
988
  },
971
989
  { type: "apply_audio_config", config: e, verify: t }
972
- ).then((i) => i === !0);
990
+ ).then((s) => s === !0);
973
991
  }
974
992
  readAudioParameter(e) {
975
993
  return this._slotRoundtrip(
@@ -994,14 +1012,14 @@ class Y extends EventTarget {
994
1012
  * inferred from the slot's resolver type at each call site) with no
995
1013
  * indexed-property casts.
996
1014
  */
997
- _slotRoundtrip(e, t, i) {
998
- return new Promise((s, o) => {
1015
+ _slotRoundtrip(e, t, s) {
1016
+ return new Promise((i, o) => {
999
1017
  if (!this._dc || this._dc.readyState !== "open") {
1000
1018
  o(new Error("Data channel not open"));
1001
1019
  return;
1002
1020
  }
1003
1021
  const n = e();
1004
- n && n(null), t(s), this._sendCommand(i);
1022
+ n && n(null), t(i), this._sendCommand(s);
1005
1023
  });
1006
1024
  }
1007
1025
  sendRaw(e) {
@@ -1010,11 +1028,11 @@ class Y extends EventTarget {
1010
1028
  subscribeLogs({ onLine: e, onError: t }) {
1011
1029
  if (typeof e != "function")
1012
1030
  throw new TypeError("subscribeLogs: onLine callback is required");
1013
- const i = { onLine: e, onError: t }, s = this._logSubscribers.size === 0;
1014
- this._logSubscribers.add(i), s && this._sendCommand({ type: "subscribe_logs" });
1031
+ const s = { onLine: e, onError: t }, i = this._logSubscribers.size === 0;
1032
+ this._logSubscribers.add(s), i && this._sendCommand({ type: "subscribe_logs" });
1015
1033
  let o = !1;
1016
1034
  return () => {
1017
- o || (o = !0, this._logSubscribers.delete(i), this._logSubscribers.size === 0 && this._sendCommand({ type: "unsubscribe_logs" }));
1035
+ o || (o = !0, this._logSubscribers.delete(s), this._logSubscribers.size === 0 && this._sendCommand({ type: "unsubscribe_logs" }));
1018
1036
  };
1019
1037
  }
1020
1038
  requestState() {
@@ -1032,21 +1050,21 @@ class Y extends EventTarget {
1032
1050
  // ─── Video helper ────────────────────────────────────────────────────
1033
1051
  attachVideo(e) {
1034
1052
  this._videoElement = e, e.muted = this._audioMuted;
1035
- const t = (s) => {
1036
- const o = s;
1053
+ const t = (i) => {
1054
+ const o = i;
1037
1055
  e.srcObject = o.detail.stream, e.playsInline = !0, "requestVideoFrameCallback" in e && this._startLatencyMonitor(e);
1038
- }, i = () => {
1056
+ }, s = () => {
1039
1057
  e.srcObject = null;
1040
1058
  };
1041
- return this.addEventListener("videoTrack", t), this.addEventListener("sessionStopped", i), () => {
1042
- this.removeEventListener("videoTrack", t), this.removeEventListener("sessionStopped", i), this._latencyMonitorId && (clearInterval(this._latencyMonitorId), this._latencyMonitorId = null), e.srcObject = null, this._videoElement = null;
1059
+ return this.addEventListener("videoTrack", t), this.addEventListener("sessionStopped", s), () => {
1060
+ this.removeEventListener("videoTrack", t), this.removeEventListener("sessionStopped", s), this._latencyMonitorId && (clearInterval(this._latencyMonitorId), this._latencyMonitorId = null), e.srcObject = null, this._videoElement = null;
1043
1061
  };
1044
1062
  }
1045
1063
  // ─── Daemon-side recorded-move playback ──────────────────────────────
1046
1064
  async playMove(e, {
1047
1065
  audioBlob: t = null,
1048
- audioLeadMs: i = -100,
1049
- description: s = "move",
1066
+ audioLeadMs: s = -100,
1067
+ description: i = "move",
1050
1068
  encoding: o = "gzip+base64",
1051
1069
  playFrequency: n = 100,
1052
1070
  initialGotoDuration: a = 0,
@@ -1062,25 +1080,25 @@ class Y extends EventTarget {
1062
1080
  throw new Error("playMove: motion must have time + set_target_data");
1063
1081
  const d = U();
1064
1082
  this._activeMoveUploadId = d;
1065
- const p = {
1066
- description: s,
1083
+ const m = {
1084
+ description: i,
1067
1085
  time: e.time,
1068
1086
  set_target_data: e.set_target_data
1069
- }, f = JSON.stringify(p);
1070
- let m, v;
1071
- o === "gzip+base64" && z() ? (m = await J(f), v = "gzip+base64") : (m = f, v = "json");
1072
- const w = Math.ceil(m.length / y) || 1;
1087
+ }, f = JSON.stringify(m);
1088
+ let p, v;
1089
+ o === "gzip+base64" && z() ? (p = await J(f), v = "gzip+base64") : (p = f, v = "json");
1090
+ const w = Math.ceil(p.length / y) || 1;
1073
1091
  c({
1074
1092
  phase: "starting",
1075
1093
  sent: 0,
1076
1094
  total: w,
1077
- bytes: m.length,
1095
+ bytes: p.length,
1078
1096
  encoding: v
1079
1097
  }), this._sendCommand({
1080
1098
  type: "upload_move_start",
1081
1099
  upload_id: d,
1082
1100
  total_chunks: w,
1083
- description: s,
1101
+ description: i,
1084
1102
  encoding: v
1085
1103
  });
1086
1104
  for (let u = 0; u < w; u++) {
@@ -1090,7 +1108,7 @@ class Y extends EventTarget {
1090
1108
  type: "upload_move_chunk",
1091
1109
  upload_id: d,
1092
1110
  chunk_index: u,
1093
- chunk: m.slice(_, _ + y)
1111
+ chunk: p.slice(_, _ + y)
1094
1112
  }), c({ phase: "upload", sent: u + 1, total: w });
1095
1113
  }
1096
1114
  if (this._sendCommand({ type: "upload_move_finish", upload_id: d }), c({ phase: "uploaded", sent: w, total: w }), t) {
@@ -1105,7 +1123,7 @@ class Y extends EventTarget {
1105
1123
  upload_id: d,
1106
1124
  total_chunks: b,
1107
1125
  encoding: "wav-base64",
1108
- description: s
1126
+ description: i
1109
1127
  });
1110
1128
  for (let I = 0; I < b; I++) {
1111
1129
  this._dc.bufferedAmount > E && await this._awaitDataChannelDrain();
@@ -1124,7 +1142,7 @@ class Y extends EventTarget {
1124
1142
  upload_id: d,
1125
1143
  play_frequency: n,
1126
1144
  initial_goto_duration: a,
1127
- audio_lead_ms: i
1145
+ audio_lead_ms: s
1128
1146
  });
1129
1147
  let g;
1130
1148
  try {
@@ -1161,16 +1179,16 @@ class Y extends EventTarget {
1161
1179
  const t = e ?? this._activeMoveUploadId;
1162
1180
  return t ? this._sendCommand({ type: "cancel_move", upload_id: t }) : !1;
1163
1181
  }
1164
- async uploadAudio(e, { description: t = "audio", onProgress: i = () => {
1182
+ async uploadAudio(e, { description: t = "audio", onProgress: s = () => {
1165
1183
  } } = {}) {
1166
1184
  if (!this._dc || this._dc.readyState !== "open")
1167
1185
  throw new Error("data channel not open");
1168
1186
  if (!(e instanceof Blob))
1169
1187
  throw new TypeError("uploadAudio: expected a Blob");
1170
- const s = U(), o = new Uint8Array(await e.arrayBuffer()), n = k(o), a = Math.ceil(n.length / y) || 1;
1171
- i({ phase: "audio-starting", sent: 0, total: a, bytes: n.length }), this._sendCommand({
1188
+ const i = U(), o = new Uint8Array(await e.arrayBuffer()), n = k(o), a = Math.ceil(n.length / y) || 1;
1189
+ s({ phase: "audio-starting", sent: 0, total: a, bytes: n.length }), this._sendCommand({
1172
1190
  type: "upload_audio_start",
1173
- upload_id: s,
1191
+ upload_id: i,
1174
1192
  total_chunks: a,
1175
1193
  encoding: "wav-base64",
1176
1194
  description: t
@@ -1180,24 +1198,24 @@ class Y extends EventTarget {
1180
1198
  const c = l * y;
1181
1199
  this._sendCommand({
1182
1200
  type: "upload_audio_chunk",
1183
- upload_id: s,
1201
+ upload_id: i,
1184
1202
  chunk_index: l,
1185
1203
  chunk: n.slice(c, c + y)
1186
- }), i({ phase: "audio-upload", sent: l + 1, total: a });
1204
+ }), s({ phase: "audio-upload", sent: l + 1, total: a });
1187
1205
  }
1188
- return this._sendCommand({ type: "upload_audio_finish", upload_id: s }), i({ phase: "audio-uploaded", sent: a, total: a }), s;
1206
+ return this._sendCommand({ type: "upload_audio_finish", upload_id: i }), s({ phase: "audio-uploaded", sent: a, total: a }), i;
1189
1207
  }
1190
1208
  async playUploadedAudio(e, { timeoutMs: t = 8e3 } = {}) {
1191
1209
  if (!this._dc || this._dc.readyState !== "open")
1192
1210
  throw new Error("data channel not open");
1193
- const i = this._waitForBroadcast(
1211
+ const s = this._waitForBroadcast(
1194
1212
  (o) => o?.type === "play_uploaded_audio" && o?.upload_id === e && (o.started === !0 || typeof o.error == "string"),
1195
1213
  { timeoutMs: t, debugLabel: "play_uploaded_audio started" }
1196
1214
  );
1197
1215
  this._sendCommand({ type: "play_uploaded_audio", upload_id: e });
1198
- const s = await i;
1199
- if (typeof s.error == "string") throw new Error(s.error);
1200
- return this._activeAudioUploadId = e, s;
1216
+ const i = await s;
1217
+ if (typeof i.error == "string") throw new Error(i.error);
1218
+ return this._activeAudioUploadId = e, i;
1201
1219
  }
1202
1220
  cancelAudio(e = null) {
1203
1221
  const t = e ?? this._activeAudioUploadId;
@@ -1207,14 +1225,14 @@ class Y extends EventTarget {
1207
1225
  _emit(e, t) {
1208
1226
  this.dispatchEvent(new CustomEvent(e, { detail: t }));
1209
1227
  }
1210
- _waitForBroadcast(e, { timeoutMs: t = 5e3, debugLabel: i = "" } = {}) {
1211
- return new Promise((s, o) => {
1228
+ _waitForBroadcast(e, { timeoutMs: t = 5e3, debugLabel: s = "" } = {}) {
1229
+ return new Promise((i, o) => {
1212
1230
  const n = {
1213
1231
  predicate: e,
1214
- resolve: s,
1232
+ resolve: i,
1215
1233
  timer: setTimeout(() => {
1216
1234
  const a = this._broadcastWaiters.indexOf(n);
1217
- a !== -1 && this._broadcastWaiters.splice(a, 1), o(new Error(`broadcast timeout (${t} ms): ${i}`));
1235
+ a !== -1 && this._broadcastWaiters.splice(a, 1), o(new Error(`broadcast timeout (${t} ms): ${s}`));
1218
1236
  }, t)
1219
1237
  };
1220
1238
  this._broadcastWaiters.push(n);
@@ -1237,13 +1255,13 @@ class Y extends EventTarget {
1237
1255
  body: JSON.stringify(e)
1238
1256
  });
1239
1257
  if (!t.ok) {
1240
- let i = "";
1258
+ let s = "";
1241
1259
  try {
1242
- i = await t.text();
1260
+ s = await t.text();
1243
1261
  } catch {
1244
1262
  }
1245
1263
  return console.warn(
1246
- `[reachy-mini] /send rejected (${t.status}) for type=${e?.type}; body=${i || "<empty>"}`
1264
+ `[reachy-mini] /send rejected (${t.status}) for type=${e?.type}; body=${s || "<empty>"}`
1247
1265
  ), null;
1248
1266
  }
1249
1267
  return await t.json();
@@ -1284,19 +1302,19 @@ class Y extends EventTarget {
1284
1302
  }
1285
1303
  }
1286
1304
  _handleEndSession(e) {
1287
- const t = e.reason, i = t === "robot_busy_local_app" ? "Robot is busy: a local Python app is running" : t === "local_app_started" ? "Disconnected: a local Python app started on the robot" : t === "robot_busy_local" ? "Robot is busy: another session is already active" : null;
1305
+ const t = e.reason, s = t === "robot_busy_local_app" ? "Robot is busy: a local Python app is running" : t === "local_app_started" ? "Disconnected: a local Python app started on the robot" : t === "robot_busy_local" ? "Robot is busy: another session is already active" : null;
1288
1306
  if (this._sessionReject) {
1289
- const s = new Error(
1290
- i || `Session ended before it could start: ${t || "unknown reason"}`
1307
+ const i = new Error(
1308
+ s || `Session ended before it could start: ${t || "unknown reason"}`
1291
1309
  );
1292
- s.reason = t ?? null, this._emit("sessionRejected", { reason: t, activeApp: null }), this._clearIceGrace(), this._uninstallNetworkListeners(), this._pc && (this._pc.close(), this._pc = null), this._micStream && (this._micStream.getTracks().forEach((n) => n.stop()), this._micStream = null), this._iceConnected = !1, this._dcOpen = !1, this._micMuted = !0, this._micSupported = !1;
1310
+ i.reason = t ?? null, this._emit("sessionRejected", { reason: t, activeApp: null }), this._clearIceGrace(), this._uninstallNetworkListeners(), this._pc && (this._pc.close(), this._pc = null), this._micStream && (this._micStream.getTracks().forEach((n) => n.stop()), this._micStream = null), this._iceConnected = !1, this._dcOpen = !1, this._micMuted = !0, this._micSupported = !1;
1293
1311
  const o = this._sessionReject;
1294
- this._sessionResolve = null, this._sessionReject = null, o(s);
1312
+ this._sessionResolve = null, this._sessionReject = null, o(i);
1295
1313
  return;
1296
1314
  }
1297
1315
  this._state === "streaming" && (this._emit("sessionStopped", {
1298
1316
  reason: t || "remote_end",
1299
- message: i
1317
+ message: s
1300
1318
  }), this.stopSession().catch(() => {
1301
1319
  }));
1302
1320
  }
@@ -1306,8 +1324,8 @@ class Y extends EventTarget {
1306
1324
  if (e.sdp) {
1307
1325
  const t = e.sdp;
1308
1326
  if (t.type === "offer") {
1309
- const s = q(t.sdp);
1310
- if (this._micSupported = s, this._emit("micSupported", { supported: s }), s && this._micStream)
1327
+ const i = q(t.sdp);
1328
+ if (this._micSupported = i, this._emit("micSupported", { supported: i }), i && this._micStream)
1311
1329
  for (const n of this._micStream.getAudioTracks())
1312
1330
  this._pc.addTrack(n, this._micStream);
1313
1331
  await this._pc.setRemoteDescription(new RTCSessionDescription(t));
@@ -1319,12 +1337,12 @@ class Y extends EventTarget {
1319
1337
  });
1320
1338
  } else
1321
1339
  await this._pc.setRemoteDescription(new RTCSessionDescription(t));
1322
- const i = this._pendingRemoteIce;
1323
- if (i && i.length) {
1340
+ const s = this._pendingRemoteIce;
1341
+ if (s && s.length) {
1324
1342
  this._pendingRemoteIce = [];
1325
- for (const s of i)
1343
+ for (const i of s)
1326
1344
  try {
1327
- await this._pc.addIceCandidate(new RTCIceCandidate(s));
1345
+ await this._pc.addIceCandidate(new RTCIceCandidate(i));
1328
1346
  } catch (o) {
1329
1347
  console.warn("[reachy-mini] buffered ICE candidate rejected:", o);
1330
1348
  }
@@ -1368,13 +1386,13 @@ class Y extends EventTarget {
1368
1386
  if ((e.command === "wake_up" || e.command === "goto_sleep") && this._pendingMotionCompletions && this._pendingMotionCompletions[e.command]) {
1369
1387
  const t = this._pendingMotionCompletions[e.command];
1370
1388
  if (e.completed === !0 && t.length > 0) {
1371
- const i = t.shift();
1372
- clearTimeout(i.timer), i.resolve();
1389
+ const s = t.shift();
1390
+ clearTimeout(s.timer), s.resolve();
1373
1391
  return;
1374
1392
  }
1375
1393
  if (e.error && t.length > 0) {
1376
- const i = t.shift();
1377
- clearTimeout(i.timer), i.reject(new Error(`${e.command}: ${e.error}`));
1394
+ const s = t.shift();
1395
+ clearTimeout(s.timer), s.reject(new Error(`${e.command}: ${e.error}`));
1378
1396
  return;
1379
1397
  }
1380
1398
  }
@@ -1382,8 +1400,8 @@ class Y extends EventTarget {
1382
1400
  for (const t of this._logSubscribers)
1383
1401
  try {
1384
1402
  t.onLine({ timestamp: e.timestamp, line: e.line });
1385
- } catch (i) {
1386
- console.error("subscribeLogs onLine threw:", i);
1403
+ } catch (s) {
1404
+ console.error("subscribeLogs onLine threw:", s);
1387
1405
  }
1388
1406
  return;
1389
1407
  }
@@ -1392,9 +1410,35 @@ class Y extends EventTarget {
1392
1410
  if (typeof t.onError == "function")
1393
1411
  try {
1394
1412
  t.onError(e.error);
1413
+ } catch (s) {
1414
+ console.error("subscribeLogs onError threw:", s);
1415
+ }
1416
+ return;
1417
+ }
1418
+ if (e.command === "start_update") {
1419
+ if (typeof e.error == "string") {
1420
+ const t = { status: "failed", error: e.error };
1421
+ for (const s of this._updateProgressSubscribers)
1422
+ try {
1423
+ s(t);
1395
1424
  } catch (i) {
1396
- console.error("subscribeLogs onError threw:", i);
1425
+ console.error("startDaemonUpdate onProgress threw:", i);
1397
1426
  }
1427
+ }
1428
+ return;
1429
+ }
1430
+ if (e.type === "update_progress") {
1431
+ const t = {
1432
+ status: e.status,
1433
+ line: typeof e.line == "string" ? e.line : void 0,
1434
+ error: typeof e.error == "string" ? e.error : void 0
1435
+ };
1436
+ for (const s of this._updateProgressSubscribers)
1437
+ try {
1438
+ s(t);
1439
+ } catch (i) {
1440
+ console.error("startDaemonUpdate onProgress threw:", i);
1441
+ }
1398
1442
  return;
1399
1443
  }
1400
1444
  if (e.state) {
@@ -1403,9 +1447,9 @@ class Y extends EventTarget {
1403
1447
  }
1404
1448
  if (e.error && this._emit("error", { source: "robot", error: e.error }), this._broadcastWaiters.length > 0)
1405
1449
  for (let t = this._broadcastWaiters.length - 1; t >= 0; t--) {
1406
- const i = this._broadcastWaiters[t];
1407
- if (i.predicate(e)) {
1408
- this._broadcastWaiters.splice(t, 1), clearTimeout(i.timer), i.resolve(e);
1450
+ const s = this._broadcastWaiters[t];
1451
+ if (s.predicate(e)) {
1452
+ this._broadcastWaiters.splice(t, 1), clearTimeout(s.timer), s.resolve(e);
1409
1453
  return;
1410
1454
  }
1411
1455
  }
@@ -1416,8 +1460,8 @@ class Y extends EventTarget {
1416
1460
  if (!e.srcObject || e.paused) return;
1417
1461
  const t = e.buffered;
1418
1462
  if (t.length > 0) {
1419
- const i = t.end(t.length - 1), s = i - e.currentTime;
1420
- s > 0.5 && (console.log(`Latency correction: was ${s.toFixed(2)}s behind`), e.currentTime = i - 0.1);
1463
+ const s = t.end(t.length - 1), i = s - e.currentTime;
1464
+ i > 0.5 && (console.log(`Latency correction: was ${i.toFixed(2)}s behind`), e.currentTime = s - 0.1);
1421
1465
  }
1422
1466
  }, 2e3);
1423
1467
  }