@pollen-robotics/reachy-mini-sdk 1.10.0-dev.0.main.e7f2273 → 1.10.0-dev.0.main.f22a526
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 +98 -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 +106 -93
- package/dist/lib/reachy-mini.d.ts.map +1 -1
- package/dist/lib/reachy-mini.js +403 -675
- 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 +226 -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/ReachyHost.d.ts.map +1 -1
- package/host/dist/chunks/centralRest-BkwTEK3Q.js +2273 -0
- package/host/dist/chunks/index-BRbvD9D5.js +821 -0
- package/host/dist/chunks/{mountHost-DztGlWwu.js → mountHost-c9m_gPPQ.js} +26739 -29305
- package/host/dist/components/DaemonUpdateGate.d.ts +65 -0
- package/host/dist/components/DaemonUpdateGate.d.ts.map +1 -0
- package/host/dist/components/ErrorView.d.ts.map +1 -1
- package/host/dist/components/HostErrorBoundary.d.ts +30 -0
- package/host/dist/components/HostErrorBoundary.d.ts.map +1 -0
- package/host/dist/components/PickerView.d.ts +16 -14
- package/host/dist/components/PickerView.d.ts.map +1 -1
- package/host/dist/components/PostOAuthSplash.d.ts +14 -9
- package/host/dist/components/PostOAuthSplash.d.ts.map +1 -1
- package/host/dist/components/ReachyHostShell.d.ts.map +1 -1
- package/host/dist/components/SignInView.d.ts.map +1 -1
- package/host/dist/components/TopBar.d.ts +5 -6
- package/host/dist/components/TopBar.d.ts.map +1 -1
- package/host/dist/embed/index.d.ts +8 -5
- package/host/dist/embed/index.d.ts.map +1 -1
- package/host/dist/entry/auto.js +2 -2
- package/host/dist/entry/embed.js +2 -2
- package/host/dist/hooks/useHostBridge.d.ts +35 -1
- package/host/dist/hooks/useHostBridge.d.ts.map +1 -1
- package/host/dist/hooks/useLatestDaemonVersion.d.ts +7 -0
- package/host/dist/hooks/useLatestDaemonVersion.d.ts.map +1 -0
- package/host/dist/hooks/useOAuth.d.ts +18 -0
- package/host/dist/hooks/useOAuth.d.ts.map +1 -1
- package/host/dist/hooks/useRobots.d.ts +0 -2
- package/host/dist/hooks/useRobots.d.ts.map +1 -1
- package/host/dist/index.js +3 -3
- package/host/dist/lib/daemonRelease.d.ts +84 -0
- package/host/dist/lib/daemonRelease.d.ts.map +1 -0
- package/host/dist/lib/protocol.d.ts +141 -7
- package/host/dist/lib/protocol.d.ts.map +1 -1
- package/host/dist/lib/rebootWatch.d.ts +51 -0
- package/host/dist/lib/rebootWatch.d.ts.map +1 -0
- package/host/dist/lib/sdk-types.d.ts +1 -1
- package/host/dist/lib/sdk-types.d.ts.map +1 -1
- package/host/dist/lib/sdkStaleness.d.ts +78 -0
- package/host/dist/lib/sdkStaleness.d.ts.map +1 -0
- package/host/dist/lib/settings.d.ts +16 -5
- package/host/dist/lib/settings.d.ts.map +1 -1
- package/package.json +9 -2
- package/host/dist/chunks/index-rW7LY0k3.js +0 -530
- package/host/dist/chunks/reachy-mini-DyEhsZnc.js +0 -1713
- package/host/dist/lib/centralListener.d.ts +0 -73
- package/host/dist/lib/centralListener.d.ts.map +0 -1
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
|
|
|
@@ -713,6 +714,7 @@ app_build_command: npm ci && npm run build # HF runs this on its builder
|
|
|
713
714
|
app_file: dist/index.html # HF serves the build output as entry
|
|
714
715
|
pinned: false
|
|
715
716
|
hf_oauth: true
|
|
717
|
+
hf_oauth_expiration_minutes: 43200 # 30 days (max); default is only 8 h
|
|
716
718
|
short_description: One-line description shown in the mobile catalog.
|
|
717
719
|
tags:
|
|
718
720
|
- reachy_mini
|
|
@@ -729,6 +731,21 @@ tags:
|
|
|
729
731
|
build completes**. For a Vite-built SPA that's `dist/index.html`.
|
|
730
732
|
- `hf_oauth: true` is what triggers `__OAUTH_CLIENT_ID__` substitution
|
|
731
733
|
inside HTML files in the served output (post-build).
|
|
734
|
+
- `hf_oauth_expiration_minutes` sets the OAuth token lifetime. Strongly
|
|
735
|
+
recommended: the default (8 hours) forces a re-auth round trip several
|
|
736
|
+
times a day. The host's silent sign-in makes those round trips
|
|
737
|
+
invisible for logged-in users, but a long-lived token avoids them
|
|
738
|
+
entirely. Two properties bound the exposure of that long-lived token:
|
|
739
|
+
- **Scope.** Don't set `hf_oauth_scopes`; the token then carries only
|
|
740
|
+
`openid profile` - identity (username, avatar) and nothing else. It
|
|
741
|
+
cannot read private repos, write to the Hub, or call the Inference
|
|
742
|
+
API, so a leaked token identifies the user but grants no account
|
|
743
|
+
access.
|
|
744
|
+
- **Idle window.** The SDK stores the token in `sessionStorage`
|
|
745
|
+
(tab-scoped, dies with the tab) and additionally drops any token
|
|
746
|
+
unused for more than 24 h (`TOKEN_MAX_IDLE_MS` in the SDK's token
|
|
747
|
+
store). A tab resurrected days later via session restore re-auths;
|
|
748
|
+
a plain reload does not.
|
|
732
749
|
- The **`reachy_mini_js_app` tag is mandatory** for mobile-catalog
|
|
733
750
|
discovery. The catalog API filters on this exact string.
|
|
734
751
|
- Apps in the `pollen-robotics/*` namespace are automatically tagged
|
|
@@ -1504,6 +1521,8 @@ before trusting the payload.
|
|
|
1504
1521
|
| host → embed | `host:leaving` | Tear-down request with `timeoutMs` |
|
|
1505
1522
|
| embed → host | `embed:request-leave` | App requests end-of-session |
|
|
1506
1523
|
| embed → host | `embed:error` | Error report (`{ message, fatal, detail? }`) |
|
|
1524
|
+
| host → embed | `host:start-update` | Run the daemon's PyPI self-update (only the embed holds a data channel) |
|
|
1525
|
+
| embed → host | `embed:update-progress` | Install progress, plus `rebooting` when the restart kills the session |
|
|
1507
1526
|
|
|
1508
1527
|
Intentionally **not** in the v1 protocol:
|
|
1509
1528
|
|
|
@@ -1531,6 +1550,31 @@ Intentionally **not** in the v1 protocol:
|
|
|
1531
1550
|
- `host:init` may arrive twice (rare: bridge re-arm); the embed
|
|
1532
1551
|
treats the latest as authoritative and re-applies theme / config.
|
|
1533
1552
|
|
|
1553
|
+
#### Daemon update gate (Mode A only)
|
|
1554
|
+
|
|
1555
|
+
The shell checks the robot's daemon version against the latest
|
|
1556
|
+
GitHub release and can interrupt the app. App authors don't wire
|
|
1557
|
+
anything up - `connectToHost()` reports the version for them - but
|
|
1558
|
+
should know the two outcomes exist:
|
|
1559
|
+
|
|
1560
|
+
- **Below `MIN_SUPPORTED_DAEMON_VERSION`** (`host/src/lib/daemonRelease.ts`):
|
|
1561
|
+
a full-screen block. Such a daemon predates the OTA command, so
|
|
1562
|
+
the only way out is the desktop app. The app never becomes
|
|
1563
|
+
interactive.
|
|
1564
|
+
- **Merely behind the latest release**: a dismissable card once the
|
|
1565
|
+
app is `live`. The app keeps running; the user may start an
|
|
1566
|
+
update, which reboots the robot and drops the session.
|
|
1567
|
+
|
|
1568
|
+
This is deliberately softer than the mobile app, which blocks on
|
|
1569
|
+
any version behind the latest. A Space is public: applying that
|
|
1570
|
+
rule here would make every daemon release a global kill switch for
|
|
1571
|
+
robots that were working a minute earlier.
|
|
1572
|
+
|
|
1573
|
+
Mode B needs none of this. The mobile app never mounts the shell -
|
|
1574
|
+
it points its iframe straight at `?embedded=1` and runs its own
|
|
1575
|
+
gate before an app can be opened - so the component simply doesn't
|
|
1576
|
+
exist in that path.
|
|
1577
|
+
|
|
1534
1578
|
### 13.7 Non-goals
|
|
1535
1579
|
|
|
1536
1580
|
To stay simple and auditable, the host shell explicitly does NOT do:
|
|
@@ -1793,3 +1837,57 @@ strict mode, silently no-op otherwise).
|
|
|
1793
1837
|
| `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
1838
|
| Carry degrees through your motion code. | Convert at the UI boundary; speak radians + magic-mm everywhere below it. |
|
|
1795
1839
|
| 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". |
|
|
1840
|
+
|
|
1841
|
+
### 14.7 Backgrounded tabs: clock robot logic off a Web Worker
|
|
1842
|
+
|
|
1843
|
+
When the user switches tabs (or the phone locks), the browser throttles
|
|
1844
|
+
your app hard: `requestAnimationFrame` **pauses entirely** and
|
|
1845
|
+
`setInterval`/`setTimeout` are clamped to **~1 tick per second**. If your
|
|
1846
|
+
pose streaming, audio pipeline, or stream-health watchdog is clocked by
|
|
1847
|
+
either of them, the robot freezes mid-motion and stalls go unnoticed until
|
|
1848
|
+
the tab comes back.
|
|
1849
|
+
|
|
1850
|
+
The fix is to split your loop in two:
|
|
1851
|
+
|
|
1852
|
+
- **Logic** (pose computation + `setTarget`, audio gain, health checks,
|
|
1853
|
+
reconnects): clock it from a **Web Worker**. Worker timers are *not*
|
|
1854
|
+
visibility-throttled, and the `message` events they post are delivered
|
|
1855
|
+
on the main thread even while the tab is hidden.
|
|
1856
|
+
- **Visuals** (DOM updates, canvas, meters): keep them on `rAF`. It pauses
|
|
1857
|
+
when hidden - which is exactly what you want for work nobody can see -
|
|
1858
|
+
and resumes on its own.
|
|
1859
|
+
|
|
1860
|
+
```ts
|
|
1861
|
+
// pose-heartbeat.worker.ts - the whole file:
|
|
1862
|
+
const INTERVAL_MS = 25; // ~40 Hz; the main thread down-samples as needed
|
|
1863
|
+
setInterval(() => postMessage(0), INTERVAL_MS);
|
|
1864
|
+
```
|
|
1865
|
+
|
|
1866
|
+
```ts
|
|
1867
|
+
// embed.ts
|
|
1868
|
+
let worker: Worker | null = null;
|
|
1869
|
+
try {
|
|
1870
|
+
worker = new Worker(new URL("./pose-heartbeat.worker.ts", import.meta.url), { type: "module" });
|
|
1871
|
+
worker.onmessage = () => stepLogic(performance.now());
|
|
1872
|
+
} catch {
|
|
1873
|
+
// Workers unavailable (rare): degrade to a throttled interval.
|
|
1874
|
+
setInterval(() => stepLogic(performance.now()), 25);
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
const renderVisuals = () => { /* DOM/canvas only - no robot commands */ };
|
|
1878
|
+
const rafLoop = () => { renderVisuals(); requestAnimationFrame(rafLoop); };
|
|
1879
|
+
requestAnimationFrame(rafLoop);
|
|
1880
|
+
```
|
|
1881
|
+
|
|
1882
|
+
Two companion rules make this robust:
|
|
1883
|
+
|
|
1884
|
+
- **Clamp your `dt`.** After a long hidden stretch the first tick sees a
|
|
1885
|
+
huge time delta; clamp it (e.g. `Math.min(dt, 100)`) so filters and
|
|
1886
|
+
interpolators don't jump.
|
|
1887
|
+
- **Resync on `visibilitychange`.** When the tab returns, call
|
|
1888
|
+
`reachy.requestState()` and run one logic step immediately instead of
|
|
1889
|
+
waiting for the next scheduled tick, so the UI repaints from fresh state.
|
|
1890
|
+
|
|
1891
|
+
Remember to `worker.terminate()` in your `onLeave` cleanup. A complete
|
|
1892
|
+
reference implementation lives in the `reachy_mini_radio_js` app
|
|
1893
|
+
(`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":""}
|