@pollen-robotics/reachy-mini-sdk 1.10.0-dev.0.main.828a720 → 1.10.0-dev.0.main.8e84768
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/APP_CREATION_GUIDE.md +55 -0
- package/dist/lib/central-listener.d.ts +71 -0
- package/dist/lib/central-listener.d.ts.map +1 -0
- package/dist/lib/central-listener.js +277 -0
- package/dist/lib/central-listener.js.map +1 -0
- package/dist/lib/central-listener.test.d.ts +2 -0
- package/dist/lib/central-listener.test.d.ts.map +1 -0
- package/dist/lib/central-listener.test.js +378 -0
- package/dist/lib/central-listener.test.js.map +1 -0
- package/dist/lib/dc-watchdog.test.d.ts +2 -0
- package/dist/lib/dc-watchdog.test.d.ts.map +1 -0
- package/dist/lib/dc-watchdog.test.js +154 -0
- package/dist/lib/dc-watchdog.test.js.map +1 -0
- package/dist/lib/ensure-awake.test.d.ts +2 -0
- package/dist/lib/ensure-awake.test.d.ts.map +1 -0
- package/dist/lib/ensure-awake.test.js +117 -0
- package/dist/lib/ensure-awake.test.js.map +1 -0
- package/dist/lib/ice-grace.test.d.ts +2 -0
- package/dist/lib/ice-grace.test.d.ts.map +1 -0
- package/dist/lib/ice-grace.test.js +211 -0
- package/dist/lib/ice-grace.test.js.map +1 -0
- package/dist/lib/imu.test.d.ts +2 -0
- package/dist/lib/imu.test.d.ts.map +1 -0
- package/dist/lib/imu.test.js +78 -0
- package/dist/lib/imu.test.js.map +1 -0
- package/dist/lib/logger.d.ts +38 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +81 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/pending-replies.d.ts +127 -0
- package/dist/lib/pending-replies.d.ts.map +1 -0
- package/dist/lib/pending-replies.js +255 -0
- package/dist/lib/pending-replies.js.map +1 -0
- package/dist/lib/pending-replies.test.d.ts +2 -0
- package/dist/lib/pending-replies.test.d.ts.map +1 -0
- package/dist/lib/pending-replies.test.js +217 -0
- package/dist/lib/pending-replies.test.js.map +1 -0
- package/dist/lib/reachy-mini.d.ts +160 -91
- package/dist/lib/reachy-mini.d.ts.map +1 -1
- package/dist/lib/reachy-mini.js +556 -612
- package/dist/lib/reachy-mini.js.map +1 -1
- package/dist/lib/redial.test.d.ts +2 -0
- package/dist/lib/redial.test.d.ts.map +1 -0
- package/dist/lib/redial.test.js +206 -0
- package/dist/lib/redial.test.js.map +1 -0
- package/dist/lib/request.test.d.ts +2 -0
- package/dist/lib/request.test.d.ts.map +1 -0
- package/dist/lib/request.test.js +90 -0
- package/dist/lib/request.test.js.map +1 -0
- package/dist/lib/session-supervisor.d.ts +201 -0
- package/dist/lib/session-supervisor.d.ts.map +1 -0
- package/dist/lib/session-supervisor.js +520 -0
- package/dist/lib/session-supervisor.js.map +1 -0
- package/dist/lib/token-store.d.ts +50 -0
- package/dist/lib/token-store.d.ts.map +1 -0
- package/dist/lib/token-store.js +120 -0
- package/dist/lib/token-store.js.map +1 -0
- package/dist/lib/token-store.test.d.ts +2 -0
- package/dist/lib/token-store.test.d.ts.map +1 -0
- package/dist/lib/token-store.test.js +82 -0
- package/dist/lib/token-store.test.js.map +1 -0
- package/dist/lib/types.d.ts +237 -6
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/url-helpers.d.ts.map +1 -1
- package/dist/lib/url-helpers.js +3 -1
- package/dist/lib/url-helpers.js.map +1 -1
- package/dist/lib/version.d.ts +2 -0
- package/dist/lib/version.d.ts.map +1 -0
- package/dist/lib/version.js +5 -0
- package/dist/lib/version.js.map +1 -0
- package/dist/reachy-mini-sdk.d.ts +15 -1
- package/dist/reachy-mini-sdk.d.ts.map +1 -1
- package/dist/reachy-mini-sdk.js +12 -0
- package/dist/reachy-mini-sdk.js.map +1 -1
- package/host/dist/chunks/{index-lMs2sCXG.js → index-rW7LY0k3.js} +209 -118
- package/host/dist/chunks/reachy-mini-DHkS9x-z.js +2145 -0
- package/host/dist/embed/index.d.ts +60 -0
- package/host/dist/embed/index.d.ts.map +1 -1
- package/host/dist/entry/auto.js +1 -1
- package/host/dist/entry/embed.d.ts +5 -4
- package/host/dist/entry/embed.d.ts.map +1 -1
- package/host/dist/entry/embed.js +5 -4
- package/host/dist/index.d.ts +2 -2
- package/host/dist/index.d.ts.map +1 -1
- package/host/dist/index.js +11 -10
- package/package.json +9 -2
- package/host/dist/chunks/reachy-mini-DwiuAr8T.js +0 -1590
package/APP_CREATION_GUIDE.md
CHANGED
|
@@ -69,6 +69,7 @@ app's UI** and nothing else.
|
|
|
69
69
|
4. [Standalone exit hooks (`installShutdownHandler`)](#144-standalone-exit-hooks-installshutdownhandler)
|
|
70
70
|
5. [Daemon parity warning](#145-daemon-parity-warning)
|
|
71
71
|
6. [Anti-patterns](#146-anti-patterns)
|
|
72
|
+
7. [Backgrounded tabs: clock robot logic off a Web Worker](#147-backgrounded-tabs-clock-robot-logic-off-a-web-worker)
|
|
72
73
|
|
|
73
74
|
---
|
|
74
75
|
|
|
@@ -1793,3 +1794,57 @@ strict mode, silently no-op otherwise).
|
|
|
1793
1794
|
| `await safelyReturnToPose(...)` expecting the move to complete. | It resolves after **dispatch**, not after motion finishes. `await sleep(plan.duration * 1000)` or subscribe to `state` if you need to wait. |
|
|
1794
1795
|
| Carry degrees through your motion code. | Convert at the UI boundary; speak radians + magic-mm everywhere below it. |
|
|
1795
1796
|
| Pass `null` head / antennas / body_yaw to opt a channel out of a `gotoTarget`. | Use `PartialPose` and **omit** the channel. The SDK treats omission as "hold previous target". |
|
|
1797
|
+
|
|
1798
|
+
### 14.7 Backgrounded tabs: clock robot logic off a Web Worker
|
|
1799
|
+
|
|
1800
|
+
When the user switches tabs (or the phone locks), the browser throttles
|
|
1801
|
+
your app hard: `requestAnimationFrame` **pauses entirely** and
|
|
1802
|
+
`setInterval`/`setTimeout` are clamped to **~1 tick per second**. If your
|
|
1803
|
+
pose streaming, audio pipeline, or stream-health watchdog is clocked by
|
|
1804
|
+
either of them, the robot freezes mid-motion and stalls go unnoticed until
|
|
1805
|
+
the tab comes back.
|
|
1806
|
+
|
|
1807
|
+
The fix is to split your loop in two:
|
|
1808
|
+
|
|
1809
|
+
- **Logic** (pose computation + `setTarget`, audio gain, health checks,
|
|
1810
|
+
reconnects): clock it from a **Web Worker**. Worker timers are *not*
|
|
1811
|
+
visibility-throttled, and the `message` events they post are delivered
|
|
1812
|
+
on the main thread even while the tab is hidden.
|
|
1813
|
+
- **Visuals** (DOM updates, canvas, meters): keep them on `rAF`. It pauses
|
|
1814
|
+
when hidden - which is exactly what you want for work nobody can see -
|
|
1815
|
+
and resumes on its own.
|
|
1816
|
+
|
|
1817
|
+
```ts
|
|
1818
|
+
// pose-heartbeat.worker.ts - the whole file:
|
|
1819
|
+
const INTERVAL_MS = 25; // ~40 Hz; the main thread down-samples as needed
|
|
1820
|
+
setInterval(() => postMessage(0), INTERVAL_MS);
|
|
1821
|
+
```
|
|
1822
|
+
|
|
1823
|
+
```ts
|
|
1824
|
+
// embed.ts
|
|
1825
|
+
let worker: Worker | null = null;
|
|
1826
|
+
try {
|
|
1827
|
+
worker = new Worker(new URL("./pose-heartbeat.worker.ts", import.meta.url), { type: "module" });
|
|
1828
|
+
worker.onmessage = () => stepLogic(performance.now());
|
|
1829
|
+
} catch {
|
|
1830
|
+
// Workers unavailable (rare): degrade to a throttled interval.
|
|
1831
|
+
setInterval(() => stepLogic(performance.now()), 25);
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
const renderVisuals = () => { /* DOM/canvas only - no robot commands */ };
|
|
1835
|
+
const rafLoop = () => { renderVisuals(); requestAnimationFrame(rafLoop); };
|
|
1836
|
+
requestAnimationFrame(rafLoop);
|
|
1837
|
+
```
|
|
1838
|
+
|
|
1839
|
+
Two companion rules make this robust:
|
|
1840
|
+
|
|
1841
|
+
- **Clamp your `dt`.** After a long hidden stretch the first tick sees a
|
|
1842
|
+
huge time delta; clamp it (e.g. `Math.min(dt, 100)`) so filters and
|
|
1843
|
+
interpolators don't jump.
|
|
1844
|
+
- **Resync on `visibilitychange`.** When the tab returns, call
|
|
1845
|
+
`reachy.requestState()` and run one logic step immediately instead of
|
|
1846
|
+
waiting for the next scheduled tick, so the UI repaints from fresh state.
|
|
1847
|
+
|
|
1848
|
+
Remember to `worker.terminate()` in your `onLeave` cleanup. A complete
|
|
1849
|
+
reference implementation lives in the `reachy_mini_radio_js` app
|
|
1850
|
+
(`src/embed.ts` + `src/pose-heartbeat.worker.ts`).
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Long-lived SSE listener channel to Hugging Face central: a realtime
|
|
3
|
+
* view of the user's robot fleet WITHOUT claiming a peer slot. The
|
|
4
|
+
* canonical implementation shared by every first-party picker / scan
|
|
5
|
+
* screen (the per-app copies used to drift).
|
|
6
|
+
*
|
|
7
|
+
* Why not just use the `ReachyMini` SDK class? Its signaling stream
|
|
8
|
+
* registers a *peer* at central, and central enforces a 1:1
|
|
9
|
+
* `token → peer_id` mapping - so a fleet-watching UI that kept an SDK
|
|
10
|
+
* instance open would clobber the peer slot the next session's WebRTC
|
|
11
|
+
* handshake needs. This module registers with `roles: ["listener"]`
|
|
12
|
+
* only, and callers `close()` it before any SDK session starts on the
|
|
13
|
+
* same token.
|
|
14
|
+
*
|
|
15
|
+
* `fetch` + manual stream reader rather than the native `EventSource`
|
|
16
|
+
* so the HF token travels in the `Authorization` header instead of
|
|
17
|
+
* the URL (no secret in DevTools / proxy logs / browser history).
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Shape of a producer entry in the SSE `list` frame and in
|
|
21
|
+
* `/api/robot-status`. Mirrors `get_producers_list` server-side.
|
|
22
|
+
* Kept loose so future fields appear without a wire bump.
|
|
23
|
+
*/
|
|
24
|
+
export interface CentralStreamProducer {
|
|
25
|
+
id: string;
|
|
26
|
+
meta?: Record<string, unknown>;
|
|
27
|
+
busy?: boolean;
|
|
28
|
+
activeApp?: string | null;
|
|
29
|
+
}
|
|
30
|
+
export interface CentralPeerStatusChangedEvent {
|
|
31
|
+
peerId: string;
|
|
32
|
+
roles: string[];
|
|
33
|
+
meta?: Record<string, unknown>;
|
|
34
|
+
}
|
|
35
|
+
export interface CentralSessionStateChangedEvent {
|
|
36
|
+
peerId: string;
|
|
37
|
+
busy: boolean;
|
|
38
|
+
activeApp?: string | null;
|
|
39
|
+
meta?: Record<string, unknown>;
|
|
40
|
+
}
|
|
41
|
+
export interface OpenCentralListenerOpts {
|
|
42
|
+
token: string;
|
|
43
|
+
/** Central base URL, e.g. from the consumer's env/config. */
|
|
44
|
+
signalingUrl: string;
|
|
45
|
+
/**
|
|
46
|
+
* `setPeerStatus(listener)` payload's `meta.name`. Surfaces in
|
|
47
|
+
* central logs as the listener identity - pass something that
|
|
48
|
+
* identifies the surface (e.g. "Reachy Mini Host (picker)").
|
|
49
|
+
*/
|
|
50
|
+
appName?: string;
|
|
51
|
+
/** First batch of producers received after each (re)connect. */
|
|
52
|
+
onList?: (producers: CentralStreamProducer[]) => void;
|
|
53
|
+
/** A robot came online / went offline (same-owner). */
|
|
54
|
+
onPeerStatusChanged?: (event: CentralPeerStatusChangedEvent) => void;
|
|
55
|
+
/** A robot transitioned busy ↔ free (same-owner). */
|
|
56
|
+
onSessionStateChanged?: (event: CentralSessionStateChangedEvent) => void;
|
|
57
|
+
/** SSE is open and `setPeerStatus(listener)` has been POSTed. */
|
|
58
|
+
onConnect?: () => void;
|
|
59
|
+
/** SSE dropped (network, central restart, scheduled reconnect). */
|
|
60
|
+
onDisconnect?: (reason: string) => void;
|
|
61
|
+
/** Non-fatal errors. Fatal auth errors end the stream and call
|
|
62
|
+
* onDisconnect with a meaningful reason instead. */
|
|
63
|
+
onError?: (err: Error) => void;
|
|
64
|
+
}
|
|
65
|
+
export interface CentralListenerHandle {
|
|
66
|
+
/** Tear the stream down. Idempotent. No further callbacks
|
|
67
|
+
* fire after this returns. */
|
|
68
|
+
close(): void;
|
|
69
|
+
}
|
|
70
|
+
export declare function openCentralListener(opts: OpenCentralListenerOpts): CentralListenerHandle;
|
|
71
|
+
//# sourceMappingURL=central-listener.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"central-listener.d.ts","sourceRoot":"","sources":["../../lib/central-listener.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAqCH;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,6BAA6B;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,+BAA+B;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,uBAAuB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,YAAY,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,qBAAqB,EAAE,KAAK,IAAI,CAAC;IACtD,uDAAuD;IACvD,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,6BAA6B,KAAK,IAAI,CAAC;IACrE,qDAAqD;IACrD,qBAAqB,CAAC,EAAE,CAAC,KAAK,EAAE,+BAA+B,KAAK,IAAI,CAAC;IACzE,iEAAiE;IACjE,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,mEAAmE;IACnE,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC;yDACqD;IACrD,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,qBAAqB;IAClC;mCAC+B;IAC/B,KAAK,IAAI,IAAI,CAAC;CACjB;AAED,wBAAgB,mBAAmB,CAC/B,IAAI,EAAE,uBAAuB,GAC9B,qBAAqB,CA6OvB"}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Long-lived SSE listener channel to Hugging Face central: a realtime
|
|
3
|
+
* view of the user's robot fleet WITHOUT claiming a peer slot. The
|
|
4
|
+
* canonical implementation shared by every first-party picker / scan
|
|
5
|
+
* screen (the per-app copies used to drift).
|
|
6
|
+
*
|
|
7
|
+
* Why not just use the `ReachyMini` SDK class? Its signaling stream
|
|
8
|
+
* registers a *peer* at central, and central enforces a 1:1
|
|
9
|
+
* `token → peer_id` mapping - so a fleet-watching UI that kept an SDK
|
|
10
|
+
* instance open would clobber the peer slot the next session's WebRTC
|
|
11
|
+
* handshake needs. This module registers with `roles: ["listener"]`
|
|
12
|
+
* only, and callers `close()` it before any SDK session starts on the
|
|
13
|
+
* same token.
|
|
14
|
+
*
|
|
15
|
+
* `fetch` + manual stream reader rather than the native `EventSource`
|
|
16
|
+
* so the HF token travels in the `Authorization` header instead of
|
|
17
|
+
* the URL (no secret in DevTools / proxy logs / browser history).
|
|
18
|
+
*/
|
|
19
|
+
const INITIAL_BACKOFF_MS = 1_000;
|
|
20
|
+
const MAX_BACKOFF_MS = 30_000;
|
|
21
|
+
const DEFAULT_LISTENER_APP_NAME = 'Reachy Mini (listener)';
|
|
22
|
+
// Heartbeat: the server runs a TTL sweeper that evicts peers whose
|
|
23
|
+
// `last_seen` is older than `LEASE_SECONDS` (30 s by default; cf.
|
|
24
|
+
// `reachy_mini_central/app.py`). `last_seen` only refreshes on inbound
|
|
25
|
+
// `POST /send`, so a pure SSE listener that registers once and stays
|
|
26
|
+
// silent gets evicted silently after one lease window - the SSE stays
|
|
27
|
+
// open but every subsequent broadcast is dropped because the server's
|
|
28
|
+
// `broadcast_to_listeners` iterates `self.peers`, which no longer
|
|
29
|
+
// contains us. Symptom: the picker sees `busy=true` when a session
|
|
30
|
+
// starts (within the first 30 s) but never receives the matching
|
|
31
|
+
// `busy=false` because the listener slot has been reaped by then.
|
|
32
|
+
//
|
|
33
|
+
// Mirror the daemon's `_heartbeat_loop` (cf. `central_signaling_relay.py`):
|
|
34
|
+
// re-emit `setPeerStatus(listener)` periodically, at the cadence the
|
|
35
|
+
// welcome recommends (`recommended_heartbeat_interval_seconds`, with a
|
|
36
|
+
// `lease_seconds / 3` fallback).
|
|
37
|
+
const HEARTBEAT_DEFAULT_INTERVAL_MS = 10_000;
|
|
38
|
+
const negotiateHeartbeatIntervalMs = (welcomeMsg) => {
|
|
39
|
+
const raw = welcomeMsg['recommended_heartbeat_interval_seconds'];
|
|
40
|
+
if (typeof raw === 'number' && Number.isFinite(raw) && raw > 0) {
|
|
41
|
+
return raw * 1000;
|
|
42
|
+
}
|
|
43
|
+
const lease = welcomeMsg['lease_seconds'];
|
|
44
|
+
if (typeof lease === 'number' && Number.isFinite(lease) && lease > 0) {
|
|
45
|
+
return (lease * 1000) / 3;
|
|
46
|
+
}
|
|
47
|
+
return HEARTBEAT_DEFAULT_INTERVAL_MS;
|
|
48
|
+
};
|
|
49
|
+
export function openCentralListener(opts) {
|
|
50
|
+
const { signalingUrl, token } = opts;
|
|
51
|
+
const appName = opts.appName ?? DEFAULT_LISTENER_APP_NAME;
|
|
52
|
+
let closed = false;
|
|
53
|
+
let abortController = null;
|
|
54
|
+
let reconnectTimer = null;
|
|
55
|
+
let heartbeatTimer = null;
|
|
56
|
+
let backoff = INITIAL_BACKOFF_MS;
|
|
57
|
+
const stopHeartbeat = () => {
|
|
58
|
+
if (heartbeatTimer !== null) {
|
|
59
|
+
clearInterval(heartbeatTimer);
|
|
60
|
+
heartbeatTimer = null;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const close = () => {
|
|
64
|
+
if (closed)
|
|
65
|
+
return;
|
|
66
|
+
closed = true;
|
|
67
|
+
stopHeartbeat();
|
|
68
|
+
if (reconnectTimer !== null) {
|
|
69
|
+
clearTimeout(reconnectTimer);
|
|
70
|
+
reconnectTimer = null;
|
|
71
|
+
}
|
|
72
|
+
if (abortController !== null) {
|
|
73
|
+
abortController.abort();
|
|
74
|
+
abortController = null;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
const scheduleReconnect = (reason) => {
|
|
78
|
+
// The `reconnectTimer` check is a re-entrancy guard: stream EOF and
|
|
79
|
+
// an in-flight heartbeat's eviction branch can both land here in the
|
|
80
|
+
// same tick, and arming twice would spawn two concurrent runStream()
|
|
81
|
+
// loops (the abortController only tracks the latest).
|
|
82
|
+
if (closed || reconnectTimer !== null)
|
|
83
|
+
return;
|
|
84
|
+
// Stop the heartbeat eagerly: the SSE is dead, so any outstanding
|
|
85
|
+
// setPeerStatus POST would fail anyway and the next welcome will
|
|
86
|
+
// start a fresh interval with a freshly-negotiated cadence.
|
|
87
|
+
stopHeartbeat();
|
|
88
|
+
opts.onDisconnect?.(reason);
|
|
89
|
+
const jitter = backoff * (Math.random() - 0.5) * 0.5;
|
|
90
|
+
const delay = Math.min(MAX_BACKOFF_MS, Math.round(backoff + jitter));
|
|
91
|
+
reconnectTimer = setTimeout(() => {
|
|
92
|
+
reconnectTimer = null;
|
|
93
|
+
backoff = Math.min(MAX_BACKOFF_MS, backoff * 2);
|
|
94
|
+
void runStream();
|
|
95
|
+
}, delay);
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* POST `setPeerStatus(roles=['listener'])`.
|
|
99
|
+
*
|
|
100
|
+
* Used both as the one-shot post-welcome registration and as the
|
|
101
|
+
* recurring heartbeat re-emission. The wire payload is identical
|
|
102
|
+
* by design: central treats repeated setPeerStatus from the same
|
|
103
|
+
* peer as idempotent and uses the inbound POST to refresh
|
|
104
|
+
* `last_seen` (cf. `signaling.touch()` in central app.py).
|
|
105
|
+
*
|
|
106
|
+
* Returns the HTTP status code on a completed round-trip (so the
|
|
107
|
+
* caller can distinguish "peer evicted server-side" from "transient
|
|
108
|
+
* network blip"), or `null` on abort / network error.
|
|
109
|
+
*/
|
|
110
|
+
const sendListenerRegistration = async () => {
|
|
111
|
+
try {
|
|
112
|
+
const resp = await fetch(`${signalingUrl}/send`, {
|
|
113
|
+
method: 'POST',
|
|
114
|
+
headers: {
|
|
115
|
+
'Content-Type': 'application/json',
|
|
116
|
+
Authorization: `Bearer ${token}`,
|
|
117
|
+
},
|
|
118
|
+
body: JSON.stringify({
|
|
119
|
+
type: 'setPeerStatus',
|
|
120
|
+
roles: ['listener'],
|
|
121
|
+
meta: { name: appName },
|
|
122
|
+
}),
|
|
123
|
+
signal: abortController?.signal,
|
|
124
|
+
});
|
|
125
|
+
if (!resp.ok) {
|
|
126
|
+
opts.onError?.(new Error(`setPeerStatus(listener) returned HTTP ${resp.status}`));
|
|
127
|
+
}
|
|
128
|
+
return resp.status;
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
if (err instanceof DOMException && err.name === 'AbortError')
|
|
132
|
+
return null;
|
|
133
|
+
opts.onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
const startHeartbeat = (intervalMs) => {
|
|
138
|
+
stopHeartbeat();
|
|
139
|
+
if (closed)
|
|
140
|
+
return;
|
|
141
|
+
heartbeatTimer = setInterval(() => {
|
|
142
|
+
if (closed)
|
|
143
|
+
return;
|
|
144
|
+
void (async () => {
|
|
145
|
+
const status = await sendListenerRegistration();
|
|
146
|
+
if (closed)
|
|
147
|
+
return;
|
|
148
|
+
// Server-side eviction: `disconnect_peer` (TTL sweeper or
|
|
149
|
+
// explicit) clears `token_to_peer`, so the next POST /send
|
|
150
|
+
// returns HTTP 400 ("Connect to /events first"). The SSE is
|
|
151
|
+
// still half-open from our side - keepalive pings keep it
|
|
152
|
+
// looking alive on the wire even though the central no longer
|
|
153
|
+
// routes broadcasts to us. Abort the dead stream AND schedule
|
|
154
|
+
// the reconnect ourselves: `runStream()` deliberately swallows
|
|
155
|
+
// aborts (they normally mean `close()`), so it would never
|
|
156
|
+
// reach its own `scheduleReconnect()` on this path. The
|
|
157
|
+
// reconnect mints a fresh peer and replays `welcome` + `list`.
|
|
158
|
+
if (status !== null && status >= 400 && status < 500) {
|
|
159
|
+
if (abortController !== null) {
|
|
160
|
+
abortController.abort();
|
|
161
|
+
}
|
|
162
|
+
scheduleReconnect(`listener evicted (HTTP ${status})`);
|
|
163
|
+
}
|
|
164
|
+
})();
|
|
165
|
+
}, intervalMs);
|
|
166
|
+
};
|
|
167
|
+
const handleMessage = (msg) => {
|
|
168
|
+
switch (msg.type) {
|
|
169
|
+
case 'welcome':
|
|
170
|
+
backoff = INITIAL_BACKOFF_MS;
|
|
171
|
+
// Register (central requires the SSE channel be established
|
|
172
|
+
// before it accepts a /send for this peer), THEN report
|
|
173
|
+
// connected - so `onConnect` consumers can immediately issue
|
|
174
|
+
// REST calls without racing the listener slot creation.
|
|
175
|
+
void (async () => {
|
|
176
|
+
await sendListenerRegistration();
|
|
177
|
+
if (closed)
|
|
178
|
+
return;
|
|
179
|
+
startHeartbeat(negotiateHeartbeatIntervalMs(msg));
|
|
180
|
+
opts.onConnect?.();
|
|
181
|
+
})();
|
|
182
|
+
return;
|
|
183
|
+
// Frames come from the first-party central, so they are cast, not
|
|
184
|
+
// field-validated — same trust level as the SDK's own signaling
|
|
185
|
+
// parser.
|
|
186
|
+
case 'list':
|
|
187
|
+
opts.onList?.(Array.isArray(msg.producers)
|
|
188
|
+
? msg.producers
|
|
189
|
+
: []);
|
|
190
|
+
return;
|
|
191
|
+
case 'peerStatusChanged':
|
|
192
|
+
opts.onPeerStatusChanged?.(msg);
|
|
193
|
+
return;
|
|
194
|
+
case 'sessionStateChanged':
|
|
195
|
+
opts.onSessionStateChanged?.(msg);
|
|
196
|
+
return;
|
|
197
|
+
// Other types (`peer`, `startSession`, `endSession`, `ping`)
|
|
198
|
+
// only fire when this peer becomes consumer / producer. As
|
|
199
|
+
// a pure listener we ignore them.
|
|
200
|
+
default:
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
const runStream = async () => {
|
|
205
|
+
if (closed)
|
|
206
|
+
return;
|
|
207
|
+
abortController = new AbortController();
|
|
208
|
+
const signal = abortController.signal;
|
|
209
|
+
let resp;
|
|
210
|
+
try {
|
|
211
|
+
resp = await fetch(`${signalingUrl}/events`, {
|
|
212
|
+
method: 'GET',
|
|
213
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
214
|
+
signal,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
catch (err) {
|
|
218
|
+
if (closed)
|
|
219
|
+
return;
|
|
220
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
221
|
+
scheduleReconnect(`fetch failed: ${reason}`);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (resp.status === 401 || resp.status === 403) {
|
|
225
|
+
opts.onError?.(new Error(`Hugging Face central rejected the token (HTTP ${resp.status})`));
|
|
226
|
+
close();
|
|
227
|
+
opts.onDisconnect?.('auth_rejected');
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (!resp.ok || resp.body === null) {
|
|
231
|
+
scheduleReconnect(`HTTP ${resp.status}`);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const reader = resp.body.getReader();
|
|
235
|
+
const decoder = new TextDecoder();
|
|
236
|
+
let buffer = '';
|
|
237
|
+
try {
|
|
238
|
+
while (true) {
|
|
239
|
+
const { done, value } = await reader.read();
|
|
240
|
+
if (done)
|
|
241
|
+
break;
|
|
242
|
+
buffer += decoder.decode(value, { stream: true });
|
|
243
|
+
const lines = buffer.split('\n');
|
|
244
|
+
buffer = lines.pop() ?? '';
|
|
245
|
+
for (const line of lines) {
|
|
246
|
+
if (!line.startsWith('data:'))
|
|
247
|
+
continue;
|
|
248
|
+
const payload = line.slice(5).trim();
|
|
249
|
+
if (payload.length === 0)
|
|
250
|
+
continue;
|
|
251
|
+
try {
|
|
252
|
+
handleMessage(JSON.parse(payload));
|
|
253
|
+
}
|
|
254
|
+
catch (err) {
|
|
255
|
+
opts.onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
// `signal.aborted` covers both `close()` and the heartbeat's
|
|
260
|
+
// eviction path (which schedules its own reconnect); some fetch
|
|
261
|
+
// implementations surface an abort as a clean EOF rather than an
|
|
262
|
+
// AbortError, so check it here too to avoid double-scheduling.
|
|
263
|
+
if (closed || signal.aborted)
|
|
264
|
+
return;
|
|
265
|
+
scheduleReconnect('stream ended');
|
|
266
|
+
}
|
|
267
|
+
catch (err) {
|
|
268
|
+
if (signal.aborted || closed)
|
|
269
|
+
return;
|
|
270
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
271
|
+
scheduleReconnect(`read failed: ${reason}`);
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
void runStream();
|
|
275
|
+
return { close };
|
|
276
|
+
}
|
|
277
|
+
//# sourceMappingURL=central-listener.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"central-listener.js","sourceRoot":"","sources":["../../lib/central-listener.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"central-listener.test.d.ts","sourceRoot":"","sources":["../../lib/central-listener.test.ts"],"names":[],"mappings":""}
|