@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.
- package/dist/lib/reachy-mini.d.ts +29 -0
- package/dist/lib/reachy-mini.d.ts.map +1 -1
- package/dist/lib/reachy-mini.js +56 -0
- package/dist/lib/reachy-mini.js.map +1 -1
- package/host/dist/chunks/{reachy-mini-GHNS4GSp.js → reachy-mini-CMGhQDtE.js} +259 -215
- package/host/dist/entry/auto.js +1 -1
- package/host/dist/entry/embed.js +1 -1
- package/host/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -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,
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
const o = [`URL: ${
|
|
7
|
-
if ((
|
|
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
|
-
|
|
9
|
+
i.message = n.error || n.message || i.message, i.data = n;
|
|
10
10
|
} else
|
|
11
|
-
|
|
12
|
-
throw
|
|
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,
|
|
16
|
-
super(
|
|
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
|
|
36
|
-
if (
|
|
37
|
-
throw new Error(`${
|
|
38
|
-
const n =
|
|
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 =
|
|
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,
|
|
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
|
|
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(
|
|
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
|
|
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,
|
|
138
|
-
if (!
|
|
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:
|
|
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
|
|
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
|
|
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",
|
|
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
|
|
212
|
-
if (
|
|
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
|
|
224
|
+
for (let s = 0; s < r.length; s += 32768)
|
|
225
225
|
e += String.fromCharCode.apply(
|
|
226
226
|
null,
|
|
227
|
-
Array.from(r.subarray(
|
|
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
|
|
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((
|
|
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((
|
|
363
|
-
console.warn("[reachy-mini] autoStartFromUrl: startSession rejected:",
|
|
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"),
|
|
378
|
-
return t &&
|
|
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 (
|
|
405
|
-
throw this._sseAbortController = null,
|
|
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((
|
|
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
|
|
420
|
+
const m = l.split(`
|
|
420
421
|
`);
|
|
421
|
-
l =
|
|
422
|
-
for (const f of
|
|
422
|
+
l = m.pop() ?? "";
|
|
423
|
+
for (const f of m)
|
|
423
424
|
if (f.startsWith("data:"))
|
|
424
425
|
try {
|
|
425
|
-
const
|
|
426
|
-
!o &&
|
|
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:
|
|
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
|
-
|
|
437
|
+
i(h);
|
|
437
438
|
return;
|
|
438
439
|
}
|
|
439
440
|
}
|
|
440
|
-
this._state !== "disconnected" && (this._state = "disconnected", this._emit("disconnected", { reason: "SSE closed" })), o ||
|
|
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:
|
|
448
|
-
autoPickIfSingle:
|
|
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 (
|
|
482
|
+
if (i && h.length === 1 && !h[0].busy)
|
|
482
483
|
l = h[0].id, c = h[0].name;
|
|
483
|
-
else if (
|
|
484
|
-
const d = await
|
|
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((
|
|
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
|
|
510
|
-
for (const o of
|
|
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
|
-
|
|
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(
|
|
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((
|
|
527
|
-
id:
|
|
528
|
-
name:
|
|
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:
|
|
532
|
+
meta: s.meta ?? {},
|
|
532
533
|
lastSeenAgeSeconds: null
|
|
533
534
|
}));
|
|
534
535
|
}
|
|
535
536
|
}
|
|
536
537
|
_waitForRobotInList(e, t) {
|
|
537
|
-
return this._robots?.find((
|
|
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),
|
|
540
|
+
this._robots?.find((a) => a.id === e) && (this.removeEventListener("robotsChanged", o), clearTimeout(n), s());
|
|
540
541
|
}, n = setTimeout(() => {
|
|
541
|
-
this.removeEventListener("robotsChanged", o),
|
|
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,
|
|
551
|
-
if (!
|
|
552
|
-
const
|
|
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,
|
|
564
|
-
this._sessionResolve = t, this._sessionReject =
|
|
565
|
-
if (
|
|
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
|
-
|
|
569
|
+
i.receiver.jitterBufferTarget = o;
|
|
569
570
|
} catch {
|
|
570
571
|
}
|
|
571
572
|
try {
|
|
572
|
-
|
|
573
|
+
i.receiver.playoutDelayHint = o / 1e3;
|
|
573
574
|
} catch {
|
|
574
575
|
}
|
|
575
|
-
this._emit("videoTrack", { track:
|
|
576
|
+
this._emit("videoTrack", { track: i.track, stream: i.streams[0] });
|
|
576
577
|
}
|
|
577
|
-
}, this._pc.onicecandidate = async (
|
|
578
|
-
|
|
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:
|
|
583
|
-
sdpMLineIndex:
|
|
584
|
-
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
|
|
589
|
-
if (
|
|
590
|
-
if (this._emit("iceStateChange", { state:
|
|
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 (
|
|
595
|
-
typeof document < "u" && document.hidden ? this._armIceGraceOnVisibility() : this._scheduleIceGrace(
|
|
595
|
+
if (i === "disconnected") {
|
|
596
|
+
typeof document < "u" && document.hidden ? this._armIceGraceOnVisibility() : this._scheduleIceGrace(P, "disconnected");
|
|
596
597
|
return;
|
|
597
598
|
}
|
|
598
|
-
if (
|
|
599
|
-
this._scheduleIceGrace(
|
|
599
|
+
if (i === "failed") {
|
|
600
|
+
this._scheduleIceGrace(j, "failed");
|
|
600
601
|
return;
|
|
601
602
|
}
|
|
602
603
|
}
|
|
603
|
-
}, this._pc.ondatachannel = (
|
|
604
|
-
this._dc =
|
|
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((
|
|
608
|
-
if (
|
|
609
|
-
this._failSessionRejected(
|
|
608
|
+
}, this._sendToServer({ type: "startSession", peerId: e }).then((i) => {
|
|
609
|
+
if (i?.type === "sessionRejected") {
|
|
610
|
+
this._failSessionRejected(i);
|
|
610
611
|
return;
|
|
611
612
|
}
|
|
612
|
-
|
|
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((
|
|
621
|
-
const
|
|
622
|
-
this._sessionResolve = null, this._sessionReject = null,
|
|
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
|
|
664
|
+
const s = this._iceGraceReason;
|
|
664
665
|
this._iceGraceReason = null;
|
|
665
|
-
const
|
|
666
|
-
if (!(
|
|
667
|
-
if (
|
|
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 (
|
|
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
|
|
693
|
-
if (!(
|
|
693
|
+
const s = this._pc.iceConnectionState;
|
|
694
|
+
if (!(s === "connected" || s === "completed")) {
|
|
694
695
|
if (Date.now() - e > X) {
|
|
695
|
-
const
|
|
696
|
+
const i = new Error(
|
|
696
697
|
"Session expired while tab was backgrounded"
|
|
697
698
|
);
|
|
698
|
-
this._sessionReject && (this._sessionReject(
|
|
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 (
|
|
702
|
-
this._scheduleIceGrace(
|
|
702
|
+
if (s === "failed") {
|
|
703
|
+
this._scheduleIceGrace(j, "failed");
|
|
703
704
|
return;
|
|
704
705
|
}
|
|
705
|
-
this._scheduleIceGrace(
|
|
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
|
|
735
|
-
if (
|
|
736
|
-
const
|
|
737
|
-
effectiveType:
|
|
738
|
-
downlink:
|
|
739
|
-
rtt:
|
|
740
|
-
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
|
-
|
|
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:
|
|
796
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
810
|
+
i.antennas = t;
|
|
810
811
|
}
|
|
811
|
-
if (
|
|
812
|
-
if (!Number.isFinite(
|
|
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 ${
|
|
815
|
+
`setTarget: body_yaw must be a finite number (radians); got ${s}`
|
|
815
816
|
);
|
|
816
|
-
|
|
817
|
+
i.body_yaw = s;
|
|
817
818
|
}
|
|
818
|
-
return this._sendCommand(
|
|
819
|
+
return this._sendCommand(i);
|
|
819
820
|
}
|
|
820
|
-
gotoTarget({ head: e, antennas: t, body_yaw:
|
|
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 (
|
|
837
|
-
if (!Number.isFinite(
|
|
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 ${
|
|
840
|
+
`gotoTarget: body_yaw must be a finite number (radians); got ${s}`
|
|
840
841
|
);
|
|
841
|
-
o.body_yaw =
|
|
842
|
+
o.body_yaw = s;
|
|
842
843
|
}
|
|
843
|
-
if (!Number.isFinite(
|
|
844
|
+
if (!Number.isFinite(i) || i <= 0)
|
|
844
845
|
throw new TypeError(
|
|
845
|
-
`gotoTarget: duration must be a positive finite number (seconds); got ${
|
|
846
|
+
`gotoTarget: duration must be a positive finite number (seconds); got ${i}`
|
|
846
847
|
);
|
|
847
|
-
return o.duration =
|
|
848
|
+
return o.duration = i, this._sendCommand(o);
|
|
848
849
|
}
|
|
849
|
-
setHeadRpyDeg(e, t,
|
|
850
|
-
return this.setTarget({ head: F(e, t,
|
|
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((
|
|
895
|
+
return this._sendCommand({ type: e }) ? new Promise((s, i) => {
|
|
878
896
|
const o = {
|
|
879
|
-
resolve:
|
|
880
|
-
reject:
|
|
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),
|
|
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
|
|
892
|
-
for (;
|
|
893
|
-
const
|
|
894
|
-
clearTimeout(
|
|
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
|
|
905
|
-
this.removeEventListener("state",
|
|
906
|
-
},
|
|
907
|
-
this.addEventListener("state",
|
|
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
|
-
(
|
|
969
|
-
this._applyAudioConfigResolve =
|
|
986
|
+
(s) => {
|
|
987
|
+
this._applyAudioConfigResolve = s;
|
|
970
988
|
},
|
|
971
989
|
{ type: "apply_audio_config", config: e, verify: t }
|
|
972
|
-
).then((
|
|
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,
|
|
998
|
-
return new Promise((
|
|
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(
|
|
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
|
|
1014
|
-
this._logSubscribers.add(
|
|
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(
|
|
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 = (
|
|
1036
|
-
const o =
|
|
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
|
-
},
|
|
1056
|
+
}, s = () => {
|
|
1039
1057
|
e.srcObject = null;
|
|
1040
1058
|
};
|
|
1041
|
-
return this.addEventListener("videoTrack", t), this.addEventListener("sessionStopped",
|
|
1042
|
-
this.removeEventListener("videoTrack", t), this.removeEventListener("sessionStopped",
|
|
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:
|
|
1049
|
-
description:
|
|
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
|
|
1066
|
-
description:
|
|
1083
|
+
const m = {
|
|
1084
|
+
description: i,
|
|
1067
1085
|
time: e.time,
|
|
1068
1086
|
set_target_data: e.set_target_data
|
|
1069
|
-
}, f = JSON.stringify(
|
|
1070
|
-
let
|
|
1071
|
-
o === "gzip+base64" && z() ? (
|
|
1072
|
-
const w = Math.ceil(
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
1171
|
-
|
|
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:
|
|
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:
|
|
1201
|
+
upload_id: i,
|
|
1184
1202
|
chunk_index: l,
|
|
1185
1203
|
chunk: n.slice(c, c + y)
|
|
1186
|
-
}),
|
|
1204
|
+
}), s({ phase: "audio-upload", sent: l + 1, total: a });
|
|
1187
1205
|
}
|
|
1188
|
-
return this._sendCommand({ type: "upload_audio_finish", upload_id:
|
|
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
|
|
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
|
|
1199
|
-
if (typeof
|
|
1200
|
-
return this._activeAudioUploadId = e,
|
|
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:
|
|
1211
|
-
return new Promise((
|
|
1228
|
+
_waitForBroadcast(e, { timeoutMs: t = 5e3, debugLabel: s = "" } = {}) {
|
|
1229
|
+
return new Promise((i, o) => {
|
|
1212
1230
|
const n = {
|
|
1213
1231
|
predicate: e,
|
|
1214
|
-
resolve:
|
|
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): ${
|
|
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
|
|
1258
|
+
let s = "";
|
|
1241
1259
|
try {
|
|
1242
|
-
|
|
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=${
|
|
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,
|
|
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
|
|
1290
|
-
|
|
1307
|
+
const i = new Error(
|
|
1308
|
+
s || `Session ended before it could start: ${t || "unknown reason"}`
|
|
1291
1309
|
);
|
|
1292
|
-
|
|
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(
|
|
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:
|
|
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
|
|
1310
|
-
if (this._micSupported =
|
|
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
|
|
1323
|
-
if (
|
|
1340
|
+
const s = this._pendingRemoteIce;
|
|
1341
|
+
if (s && s.length) {
|
|
1324
1342
|
this._pendingRemoteIce = [];
|
|
1325
|
-
for (const
|
|
1343
|
+
for (const i of s)
|
|
1326
1344
|
try {
|
|
1327
|
-
await this._pc.addIceCandidate(new RTCIceCandidate(
|
|
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
|
|
1372
|
-
clearTimeout(
|
|
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
|
|
1377
|
-
clearTimeout(
|
|
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 (
|
|
1386
|
-
console.error("subscribeLogs onLine threw:",
|
|
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("
|
|
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
|
|
1407
|
-
if (
|
|
1408
|
-
this._broadcastWaiters.splice(t, 1), clearTimeout(
|
|
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
|
|
1420
|
-
|
|
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
|
}
|