@pollen-robotics/reachy-mini-sdk 1.7.3-main.b44388c
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/CHANGELOG.md +188 -0
- package/LICENSE +201 -0
- package/README.md +129 -0
- package/host/APP_AUTHOR_GUIDE.md +646 -0
- package/host/README.md +155 -0
- package/host/SPEC.md +618 -0
- package/host/dist/ReachyHost.d.ts +39 -0
- package/host/dist/ReachyHost.d.ts.map +1 -0
- package/host/dist/assets/index.d.ts +16 -0
- package/host/dist/assets/index.d.ts.map +1 -0
- package/host/dist/chunks/index-C3B3FuBC.js +402 -0
- package/host/dist/chunks/index-C3B3FuBC.js.map +1 -0
- package/host/dist/chunks/mountHost-8f-laxwI.js +48280 -0
- package/host/dist/chunks/mountHost-8f-laxwI.js.map +1 -0
- package/host/dist/chunks/reachy-mini-sdk-CM-WMoGJ.js +1425 -0
- package/host/dist/chunks/reachy-mini-sdk-CM-WMoGJ.js.map +1 -0
- package/host/dist/components/ConnectingView.d.ts +13 -0
- package/host/dist/components/ConnectingView.d.ts.map +1 -0
- package/host/dist/components/EmbedFrame.d.ts +16 -0
- package/host/dist/components/EmbedFrame.d.ts.map +1 -0
- package/host/dist/components/ErrorView.d.ts +21 -0
- package/host/dist/components/ErrorView.d.ts.map +1 -0
- package/host/dist/components/LeavingView.d.ts +33 -0
- package/host/dist/components/LeavingView.d.ts.map +1 -0
- package/host/dist/components/PickerView.d.ts +51 -0
- package/host/dist/components/PickerView.d.ts.map +1 -0
- package/host/dist/components/ReachyHostShell.d.ts +21 -0
- package/host/dist/components/ReachyHostShell.d.ts.map +1 -0
- package/host/dist/components/SignInView.d.ts +32 -0
- package/host/dist/components/SignInView.d.ts.map +1 -0
- package/host/dist/components/StepsProgressIndicator.d.ts +41 -0
- package/host/dist/components/StepsProgressIndicator.d.ts.map +1 -0
- package/host/dist/components/TopBar.d.ts +52 -0
- package/host/dist/components/TopBar.d.ts.map +1 -0
- package/host/dist/components/WelcomeBackOverlay.d.ts +28 -0
- package/host/dist/components/WelcomeBackOverlay.d.ts.map +1 -0
- package/host/dist/embed/index.d.ts +150 -0
- package/host/dist/embed/index.d.ts.map +1 -0
- package/host/dist/entry/auto.d.ts +12 -0
- package/host/dist/entry/auto.d.ts.map +1 -0
- package/host/dist/entry/auto.js +7 -0
- package/host/dist/entry/auto.js.map +1 -0
- package/host/dist/entry/embed.d.ts +12 -0
- package/host/dist/entry/embed.d.ts.map +1 -0
- package/host/dist/entry/embed.js +7 -0
- package/host/dist/entry/embed.js.map +1 -0
- package/host/dist/hooks/useHfProfile.d.ts +7 -0
- package/host/dist/hooks/useHfProfile.d.ts.map +1 -0
- package/host/dist/hooks/useHostBridge.d.ts +37 -0
- package/host/dist/hooks/useHostBridge.d.ts.map +1 -0
- package/host/dist/hooks/useOAuth.d.ts +16 -0
- package/host/dist/hooks/useOAuth.d.ts.map +1 -0
- package/host/dist/hooks/useRobots.d.ts +17 -0
- package/host/dist/hooks/useRobots.d.ts.map +1 -0
- package/host/dist/hooks/useSdk.d.ts +16 -0
- package/host/dist/hooks/useSdk.d.ts.map +1 -0
- package/host/dist/index.d.ts +25 -0
- package/host/dist/index.d.ts.map +1 -0
- package/host/dist/index.js +15 -0
- package/host/dist/index.js.map +1 -0
- package/host/dist/lib/centralListener.d.ts +73 -0
- package/host/dist/lib/centralListener.d.ts.map +1 -0
- package/host/dist/lib/centralRest.d.ts +35 -0
- package/host/dist/lib/centralRest.d.ts.map +1 -0
- package/host/dist/lib/protocol.d.ts +230 -0
- package/host/dist/lib/protocol.d.ts.map +1 -0
- package/host/dist/lib/protocol.js +48 -0
- package/host/dist/lib/protocol.js.map +1 -0
- package/host/dist/lib/sdk-types.d.ts +46 -0
- package/host/dist/lib/sdk-types.d.ts.map +1 -0
- package/host/dist/lib/settings.d.ts +69 -0
- package/host/dist/lib/settings.d.ts.map +1 -0
- package/host/dist/lib/signalingUrl.d.ts +28 -0
- package/host/dist/lib/signalingUrl.d.ts.map +1 -0
- package/host/dist/lib/theme.d.ts +4 -0
- package/host/dist/lib/theme.d.ts.map +1 -0
- package/host/dist/lib/themeMode.d.ts +9 -0
- package/host/dist/lib/themeMode.d.ts.map +1 -0
- package/host/dist/lib/tokens.d.ts +64 -0
- package/host/dist/lib/tokens.d.ts.map +1 -0
- package/host/dist/mountHost.d.ts +36 -0
- package/host/dist/mountHost.d.ts.map +1 -0
- package/package.json +109 -0
- package/reachy-mini-sdk.d.ts +285 -0
- package/reachy-mini-sdk.js +2164 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { R as o } from "./chunks/reachy-mini-sdk-CM-WMoGJ.js";
|
|
2
|
+
import { R as s, m as a } from "./chunks/mountHost-8f-laxwI.js";
|
|
3
|
+
import { c as t } from "./chunks/index-C3B3FuBC.js";
|
|
4
|
+
import { PROTOCOL_VERSION as c, decodeCredsFromHash as m, encodeCredsToHash as w, isProtocolMessage as h } from "./lib/protocol.js";
|
|
5
|
+
typeof window < "u" && !window.ReachyMini && (window.ReachyMini = o, window.dispatchEvent(new Event("reachymini:ready")));
|
|
6
|
+
export {
|
|
7
|
+
c as PROTOCOL_VERSION,
|
|
8
|
+
s as ReachyHost,
|
|
9
|
+
t as connectToHost,
|
|
10
|
+
m as decodeCredsFromHash,
|
|
11
|
+
w as encodeCredsToHash,
|
|
12
|
+
h as isProtocolMessage,
|
|
13
|
+
a as mountHost
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["/**\n * npm entry point for the `./host` subpath.\n *\n * import { mountHost, connectToHost } from '@pollen-robotics/reachy-mini-sdk/host';\n *\n * works for IDE autocomplete and for app authors who prefer the\n * package directly over the CDN `host/auto` bundle.\n *\n * Importing this module also auto-installs the SDK constructor on\n * `window.ReachyMini` (when unset) and dispatches the\n * `reachymini:ready` event the host's wait loops listen for.\n * Apps consuming the host via npm therefore no longer need a\n * separate jsdelivr `<script type=\"module\">` to expose the SDK\n * on the global — installing this package is enough.\n */\n\nimport { ReachyMini } from '../../reachy-mini-sdk';\n\nif (typeof window !== 'undefined' && !window.ReachyMini) {\n window.ReachyMini = ReachyMini;\n window.dispatchEvent(new Event('reachymini:ready'));\n}\n\nexport { ReachyHost } from './ReachyHost';\nexport type { ReachyHostProps } from './ReachyHost';\n\nexport { mountHost } from './mountHost';\nexport type { MountHostOptions, MountedHost } from './mountHost';\n\nexport { connectToHost } from './embed';\nexport type { ConnectedHandle, ConnectToHostOptions } from './embed';\n\n// Protocol types (useful for app authors implementing custom\n// flows or unit-testing their dispatcher).\nexport type {\n AppConnectingStep,\n AppPhase,\n ConfigPayload,\n CredsBundle,\n EmbedToHostMsg,\n HostToEmbedMsg,\n LeavingReason,\n ThemeMode,\n} from './lib/protocol';\nexport {\n PROTOCOL_VERSION,\n decodeCredsFromHash,\n encodeCredsToHash,\n isProtocolMessage,\n} from './lib/protocol';\n\n// SDK types (re-export so app authors can write\n// `import type { ReachyMiniInstance } from '@pollen-robotics/reachy-mini-sdk/host'`.\nexport type {\n ReachyMiniInstance,\n ReachyMiniOptions,\n ReachyMiniConstructor,\n RobotInfo,\n} from './lib/sdk-types';\n"],"names":["ReachyMini"],"mappings":";;;;AAkBI,OAAO,SAAW,OAAe,CAAC,OAAO,eAC3C,OAAO,aAAaA,GACpB,OAAO,cAAc,IAAI,MAAM,kBAAkB,CAAC;"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Long-lived SSE listener channel to Hugging Face central.
|
|
3
|
+
*
|
|
4
|
+
* Ported from the Reachy Mini mobile app (`centralListenerStream.ts`).
|
|
5
|
+
* Why this exists and how it relates to the SDK is documented in
|
|
6
|
+
* the docstring of `useRobots.ts`; in short:
|
|
7
|
+
*
|
|
8
|
+
* - The host shell needs realtime updates of the user's robot
|
|
9
|
+
* fleet (online/offline, busy/free).
|
|
10
|
+
* - The ReachyMini SDK can do this, but only by also registering
|
|
11
|
+
* a peer slot at central, which then conflicts with the
|
|
12
|
+
* iframe's session WebRTC handshake.
|
|
13
|
+
* - Solution: open a *bare* SSE channel here, registered with
|
|
14
|
+
* `roles: ["listener"]` only. On `close()` (when the user
|
|
15
|
+
* selects a robot and we mount the iframe) central retires
|
|
16
|
+
* the listener slot via its `token → peer_id` 1:1 mapping, so
|
|
17
|
+
* the iframe's SDK opens a fresh slot without contention.
|
|
18
|
+
*
|
|
19
|
+
* Wire surface we listen for
|
|
20
|
+
* ──────────────────────────
|
|
21
|
+
* - `welcome` opening handshake, includes the assigned peerId
|
|
22
|
+
* - `list` initial robot snapshot
|
|
23
|
+
* - `peerStatusChanged` a robot came online / went offline
|
|
24
|
+
* - `sessionStateChanged` busy / free transition
|
|
25
|
+
*
|
|
26
|
+
* Reconnect policy
|
|
27
|
+
* ────────────────
|
|
28
|
+
* Network blips, HF Space cold-restarts, and proxy idle culls all
|
|
29
|
+
* surface as a stream EOF or fetch error. We retry with exponential
|
|
30
|
+
* backoff capped at `MAX_BACKOFF_MS`, jittered to avoid thundering
|
|
31
|
+
* herds on a central restart.
|
|
32
|
+
*/
|
|
33
|
+
export interface CentralStreamProducer {
|
|
34
|
+
id: string;
|
|
35
|
+
meta?: Record<string, unknown>;
|
|
36
|
+
busy?: boolean;
|
|
37
|
+
activeApp?: string | null;
|
|
38
|
+
}
|
|
39
|
+
export interface CentralListEvent {
|
|
40
|
+
producers: CentralStreamProducer[];
|
|
41
|
+
}
|
|
42
|
+
export interface CentralPeerStatusChangedEvent {
|
|
43
|
+
peerId: string;
|
|
44
|
+
roles: string[];
|
|
45
|
+
meta?: Record<string, unknown>;
|
|
46
|
+
}
|
|
47
|
+
export interface CentralSessionStateChangedEvent {
|
|
48
|
+
peerId: string;
|
|
49
|
+
busy: boolean;
|
|
50
|
+
activeApp: string | null;
|
|
51
|
+
meta?: Record<string, unknown>;
|
|
52
|
+
}
|
|
53
|
+
export interface OpenCentralListenerOpts {
|
|
54
|
+
token: string;
|
|
55
|
+
signalingUrl: string;
|
|
56
|
+
onList?: (event: CentralListEvent) => void;
|
|
57
|
+
onPeerStatusChanged?: (event: CentralPeerStatusChangedEvent) => void;
|
|
58
|
+
onSessionStateChanged?: (event: CentralSessionStateChangedEvent) => void;
|
|
59
|
+
/** SSE is open and `setPeerStatus(listener)` has been POSTed. */
|
|
60
|
+
onConnect?: () => void;
|
|
61
|
+
/** SSE dropped (network, central restart, scheduled reconnect). */
|
|
62
|
+
onDisconnect?: (reason: string) => void;
|
|
63
|
+
/** Non-fatal errors. Fatal auth errors end the stream and call
|
|
64
|
+
* onDisconnect with a meaningful reason instead. */
|
|
65
|
+
onError?: (err: Error) => void;
|
|
66
|
+
}
|
|
67
|
+
export interface CentralListenerHandle {
|
|
68
|
+
/** Tear the stream down. Idempotent. No further callbacks
|
|
69
|
+
* fire after this returns. */
|
|
70
|
+
close(): void;
|
|
71
|
+
}
|
|
72
|
+
export declare function openCentralListener(opts: OpenCentralListenerOpts): CentralListenerHandle;
|
|
73
|
+
//# sourceMappingURL=centralListener.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"centralListener.d.ts","sourceRoot":"","sources":["../../src/lib/centralListener.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAgDH,MAAM,WAAW,qBAAqB;IACpC,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;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,qBAAqB,EAAE,CAAC;CACpC;AAED,MAAM,WAAW,6BAA6B;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,+BAA+B;IAC9C,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC3C,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,6BAA6B,KAAK,IAAI,CAAC;IACrE,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;CAChC;AAED,MAAM,WAAW,qBAAqB;IACpC;mCAC+B;IAC/B,KAAK,IAAI,IAAI,CAAC;CACf;AAED,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,uBAAuB,GAC5B,qBAAqB,CAsOvB"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Direct REST access to the HF central signaling server.
|
|
3
|
+
*
|
|
4
|
+
* The picker needs a list of robots, but opening an SSE
|
|
5
|
+
* connection from the host shell (via `sdk.connect()`) breaks
|
|
6
|
+
* the iframe's WebRTC handshake: central can't reliably route
|
|
7
|
+
* the daemon's SDP offer once two SSE streams have been
|
|
8
|
+
* registered for the same HF token, even after one disconnects.
|
|
9
|
+
*
|
|
10
|
+
* Central exposes a public `GET /api/robot-status` endpoint
|
|
11
|
+
* authenticated by Bearer token that returns the same producer
|
|
12
|
+
* list as `setPeerStatus → list`, without requiring an SSE
|
|
13
|
+
* registration. The mobile app uses this for "remote mode" and
|
|
14
|
+
* we reuse the exact same wire shape here.
|
|
15
|
+
*/
|
|
16
|
+
import type { RobotInfo } from './sdk-types';
|
|
17
|
+
export interface FetchRobotsResult {
|
|
18
|
+
ok: boolean;
|
|
19
|
+
robots: RobotInfo[];
|
|
20
|
+
reason?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Fetch the user's robot list directly from central.
|
|
24
|
+
*
|
|
25
|
+
* @param signalingUrl Base URL of the central server (e.g.
|
|
26
|
+
* `https://pollen-robotics-reachy-mini-central.hf.space`)
|
|
27
|
+
* @param hfToken Hugging Face user token
|
|
28
|
+
* @param signal Optional AbortSignal for cancellation
|
|
29
|
+
*/
|
|
30
|
+
export declare function fetchRobotsFromCentral(opts: {
|
|
31
|
+
signalingUrl: string;
|
|
32
|
+
hfToken: string;
|
|
33
|
+
signal?: AbortSignal;
|
|
34
|
+
}): Promise<FetchRobotsResult>;
|
|
35
|
+
//# sourceMappingURL=centralRest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"centralRest.d.ts","sourceRoot":"","sources":["../../src/lib/centralRest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAmB7C,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAkCD;;;;;;;GAOG;AACH,wBAAsB,sBAAsB,CAAC,IAAI,EAAE;IACjD,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAmD7B"}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* postMessage protocol v1 between the host shell (parent window,
|
|
3
|
+
* exposed via `@pollen-robotics/reachy-mini-sdk/host`) and an
|
|
4
|
+
* embedded Reachy Mini app (iframe).
|
|
5
|
+
*
|
|
6
|
+
* Canonical reference: SPEC.md §6.
|
|
7
|
+
*
|
|
8
|
+
* Contract identity
|
|
9
|
+
* ─────────────────
|
|
10
|
+
* - Every message carries `version: 1`. Bumping that integer is
|
|
11
|
+
* the ONLY way to introduce a breaking change to the wire
|
|
12
|
+
* protocol. Additive changes (new optional fields, new typed
|
|
13
|
+
* messages) ship without a version bump.
|
|
14
|
+
* - Every message carries `source: 'reachy-mini'`. Lets receivers
|
|
15
|
+
* distinguish our envelopes from unrelated `postMessage` traffic
|
|
16
|
+
* (DevTools, MUI portals, browser extensions, ...).
|
|
17
|
+
* - Both sides validate `event.origin` against the expected origin
|
|
18
|
+
* before trusting the payload. In our deployment (same-origin
|
|
19
|
+
* iframe within an HF Space) the expected origin is
|
|
20
|
+
* `event.origin === window.location.origin`.
|
|
21
|
+
*
|
|
22
|
+
* Message families
|
|
23
|
+
* ────────────────
|
|
24
|
+
* 1. Lifecycle: boot / ready / leaving. Drive the visible state
|
|
25
|
+
* of the host shell.
|
|
26
|
+
* 2. State: `embed:app-state` lets the host render accurate
|
|
27
|
+
* connection / wake-up overlays.
|
|
28
|
+
* 3. Config & theme: opaque `config` payload + theme push.
|
|
29
|
+
* 4. Error: `embed:error` for tear-down on app failure.
|
|
30
|
+
*
|
|
31
|
+
* Intentionally NOT in v1 (see SPEC §6 and §11):
|
|
32
|
+
* - No `host:custom` / `embed:custom` free-form channel.
|
|
33
|
+
* - No `embed:request-config-update` (apps don't push config).
|
|
34
|
+
* - No heartbeat / ping-pong (backlog).
|
|
35
|
+
*/
|
|
36
|
+
/** Protocol version. Bump on breaking changes. */
|
|
37
|
+
export declare const PROTOCOL_VERSION = 1;
|
|
38
|
+
/** Source tag attached to every envelope. */
|
|
39
|
+
export declare const PROTOCOL_SOURCE: "reachy-mini";
|
|
40
|
+
/** Theme mode the host applies to the embedded app. */
|
|
41
|
+
export type ThemeMode = 'dark' | 'light';
|
|
42
|
+
/**
|
|
43
|
+
* Connection lifecycle, as observed by the embedded app. Drives
|
|
44
|
+
* the host's `ConnectingView` stepper and visibility of the
|
|
45
|
+
* iframe.
|
|
46
|
+
*
|
|
47
|
+
* boot : app loaded, no SDK action yet
|
|
48
|
+
* connecting : ReachyMini.connect() / startSession() in flight
|
|
49
|
+
* live : session up, motors awake, app interactive
|
|
50
|
+
* leaving : `host:leaving` received, app tearing down
|
|
51
|
+
* error : non-recoverable failure
|
|
52
|
+
*/
|
|
53
|
+
export type AppPhase = 'boot' | 'connecting' | 'live' | 'leaving' | 'error';
|
|
54
|
+
/**
|
|
55
|
+
* Fine-grained step inside `connecting`. Maps to the 3 dots in
|
|
56
|
+
* `StepsProgressIndicator`:
|
|
57
|
+
* - `link` : waiting for `host:init` / `connect()` in flight
|
|
58
|
+
* - `session` : `startSession()` in flight
|
|
59
|
+
* - `wake` : `ensureAwake()` in flight
|
|
60
|
+
*
|
|
61
|
+
* Apps that don't differentiate can omit this field; the host
|
|
62
|
+
* treats it as `link`.
|
|
63
|
+
*/
|
|
64
|
+
export type AppConnectingStep = 'link' | 'session' | 'wake';
|
|
65
|
+
/** Opaque app-specific payload routed through the host. */
|
|
66
|
+
export type ConfigPayload = unknown;
|
|
67
|
+
/** Reason supplied with `host:leaving` for logging. */
|
|
68
|
+
export type LeavingReason = 'user-action' | 'session-stopped' | 'error' | 'pagehide';
|
|
69
|
+
/**
|
|
70
|
+
* First message sent by the host once the iframe has shouted
|
|
71
|
+
* `embed:ready`. Carries the credentials + initial state the app
|
|
72
|
+
* needs to bring a session up.
|
|
73
|
+
*
|
|
74
|
+
* Same-origin iframe: the SDK CDN script is imported with a
|
|
75
|
+
* relative URL from `index.html`. We still pass `signalingUrl`
|
|
76
|
+
* here so the host can swap centrals (staging / self-hosted)
|
|
77
|
+
* without a rebuild of the app.
|
|
78
|
+
*/
|
|
79
|
+
export interface HostInitMsg {
|
|
80
|
+
source: typeof PROTOCOL_SOURCE;
|
|
81
|
+
type: 'host:init';
|
|
82
|
+
version: 1;
|
|
83
|
+
theme: ThemeMode;
|
|
84
|
+
signalingUrl: string;
|
|
85
|
+
/** HF OAuth bearer token. Optional for apps that don't need to
|
|
86
|
+
* authenticate (rare). */
|
|
87
|
+
hfToken?: string;
|
|
88
|
+
/** HF account user name, when known. */
|
|
89
|
+
userName?: string | null;
|
|
90
|
+
/** Robot ID selected by the host's picker. */
|
|
91
|
+
robotPeerId: string;
|
|
92
|
+
/** Optional opaque payload from `?config=<base64>` or from the
|
|
93
|
+
* mobile-app handoff. App is responsible for parsing /
|
|
94
|
+
* validating. */
|
|
95
|
+
config: ConfigPayload;
|
|
96
|
+
/** Host display name (e.g. "Reachy Mini") - useful for the
|
|
97
|
+
* embed if it wants to surface "Connected via …" copy. */
|
|
98
|
+
hostName: string;
|
|
99
|
+
/** Embedded app's display name (passed by the dispatcher). */
|
|
100
|
+
appName: string;
|
|
101
|
+
}
|
|
102
|
+
/** Theme changed live (user toggled OS palette, host UI switched). */
|
|
103
|
+
export interface HostThemeChangedMsg {
|
|
104
|
+
source: typeof PROTOCOL_SOURCE;
|
|
105
|
+
type: 'host:theme-changed';
|
|
106
|
+
version: 1;
|
|
107
|
+
theme: ThemeMode;
|
|
108
|
+
}
|
|
109
|
+
/** Config updated live without an iframe reload. */
|
|
110
|
+
export interface HostConfigChangedMsg {
|
|
111
|
+
source: typeof PROTOCOL_SOURCE;
|
|
112
|
+
type: 'host:config-changed';
|
|
113
|
+
version: 1;
|
|
114
|
+
config: ConfigPayload;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Host is asking the app to wind down cleanly. The app SHOULD:
|
|
118
|
+
* 1. Stop emitting motion commands.
|
|
119
|
+
* 2. Disconnect any non-SDK resources (timers, listeners).
|
|
120
|
+
* 3. Resolve every registered `onLeave` callback before the
|
|
121
|
+
* `timeoutMs` deadline; otherwise the host force-unmounts
|
|
122
|
+
* the iframe.
|
|
123
|
+
*
|
|
124
|
+
* The host does NOT expect an explicit `leave-ack`; resolving
|
|
125
|
+
* `onLeave` callbacks within the deadline is sufficient.
|
|
126
|
+
*/
|
|
127
|
+
export interface HostLeavingMsg {
|
|
128
|
+
source: typeof PROTOCOL_SOURCE;
|
|
129
|
+
type: 'host:leaving';
|
|
130
|
+
version: 1;
|
|
131
|
+
reason: LeavingReason;
|
|
132
|
+
/** Soft deadline in ms. After this the host unmounts the iframe
|
|
133
|
+
* regardless. */
|
|
134
|
+
timeoutMs: number;
|
|
135
|
+
}
|
|
136
|
+
export type HostToEmbedMsg = HostInitMsg | HostThemeChangedMsg | HostConfigChangedMsg | HostLeavingMsg;
|
|
137
|
+
/**
|
|
138
|
+
* Emitted by the app as early as possible (synchronous tick of
|
|
139
|
+
* the embed entry, before the SDK is touched). Tells the host
|
|
140
|
+
* the iframe is alive and ready to receive `host:init`. The host
|
|
141
|
+
* MUST NOT send `host:init` before seeing this.
|
|
142
|
+
*/
|
|
143
|
+
export interface EmbedReadyMsg {
|
|
144
|
+
source: typeof PROTOCOL_SOURCE;
|
|
145
|
+
type: 'embed:ready';
|
|
146
|
+
version: 1;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* App-level connection state. The host renders its
|
|
150
|
+
* `ConnectingView` overlay over the (still-mounted) iframe until
|
|
151
|
+
* it sees `phase === 'live'`. Apps SHOULD emit this on every
|
|
152
|
+
* transition; the host caches the last value and won't re-render
|
|
153
|
+
* unless something changes.
|
|
154
|
+
*/
|
|
155
|
+
export interface EmbedAppStateMsg {
|
|
156
|
+
source: typeof PROTOCOL_SOURCE;
|
|
157
|
+
type: 'embed:app-state';
|
|
158
|
+
version: 1;
|
|
159
|
+
phase: AppPhase;
|
|
160
|
+
/** Sub-step inside `connecting`. Ignored for other phases. */
|
|
161
|
+
connectingStep?: AppConnectingStep | null;
|
|
162
|
+
/** Optional human-readable hint shown in the overlay caption. */
|
|
163
|
+
message?: string | null;
|
|
164
|
+
}
|
|
165
|
+
/** App requests to leave (user clicked an in-app exit, error,
|
|
166
|
+
* ...). The host runs the same tear-down as a top-bar
|
|
167
|
+
* "End session". */
|
|
168
|
+
export interface EmbedRequestLeaveMsg {
|
|
169
|
+
source: typeof PROTOCOL_SOURCE;
|
|
170
|
+
type: 'embed:request-leave';
|
|
171
|
+
version: 1;
|
|
172
|
+
}
|
|
173
|
+
/** App-level error report. `fatal: true` switches the host to
|
|
174
|
+
* ErrorView; `fatal: false` is logged and may surface a toast. */
|
|
175
|
+
export interface EmbedErrorMsg {
|
|
176
|
+
source: typeof PROTOCOL_SOURCE;
|
|
177
|
+
type: 'embed:error';
|
|
178
|
+
version: 1;
|
|
179
|
+
message: string;
|
|
180
|
+
fatal: boolean;
|
|
181
|
+
detail?: unknown;
|
|
182
|
+
}
|
|
183
|
+
export type EmbedToHostMsg = EmbedReadyMsg | EmbedAppStateMsg | EmbedRequestLeaveMsg | EmbedErrorMsg;
|
|
184
|
+
/**
|
|
185
|
+
* Serialised credentials passed from the host (Mode A) or the
|
|
186
|
+
* mobile app (Mode B) to the embed via the URL hash fragment.
|
|
187
|
+
*
|
|
188
|
+
* Hash-only (never URL search): browsers don't send the hash to
|
|
189
|
+
* any server, so the HF token never appears in access logs,
|
|
190
|
+
* referer headers, or HF Spaces frontend logs.
|
|
191
|
+
*
|
|
192
|
+
* The embed wipes the hash with `history.replaceState` on its
|
|
193
|
+
* first synchronous tick, before any `await`, then proceeds with
|
|
194
|
+
* the rest of the boot using the in-memory bundle.
|
|
195
|
+
*/
|
|
196
|
+
export interface CredsBundle {
|
|
197
|
+
hfToken?: string | null;
|
|
198
|
+
userName?: string | null;
|
|
199
|
+
robotPeerId: string;
|
|
200
|
+
signalingUrl: string;
|
|
201
|
+
theme: ThemeMode;
|
|
202
|
+
config: ConfigPayload;
|
|
203
|
+
hostName: string;
|
|
204
|
+
appName: string;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Cheap discriminator: does this `unknown` look like a v1
|
|
208
|
+
* envelope from our protocol? Receivers call this before
|
|
209
|
+
* narrowing on `type`. Conservative on the version: an unknown
|
|
210
|
+
* version is treated as "not our protocol" so the receiver
|
|
211
|
+
* silently ignores it (forward-compat for a future v2 peer).
|
|
212
|
+
*/
|
|
213
|
+
export declare function isProtocolMessage(value: unknown): value is {
|
|
214
|
+
source: typeof PROTOCOL_SOURCE;
|
|
215
|
+
type: string;
|
|
216
|
+
version: number;
|
|
217
|
+
};
|
|
218
|
+
/**
|
|
219
|
+
* Encode a creds bundle to the URL hash fragment.
|
|
220
|
+
* URL-safe base64 wrapper around JSON to avoid percent-encoding
|
|
221
|
+
* noise.
|
|
222
|
+
*/
|
|
223
|
+
export declare function encodeCredsToHash(bundle: CredsBundle): string;
|
|
224
|
+
/**
|
|
225
|
+
* Decode the creds bundle from a URL hash fragment. Returns
|
|
226
|
+
* `null` if no `creds=` segment is present or if the payload is
|
|
227
|
+
* malformed (caller handles the error by rendering ErrorView).
|
|
228
|
+
*/
|
|
229
|
+
export declare function decodeCredsFromHash(hash: string | null): CredsBundle | null;
|
|
230
|
+
//# sourceMappingURL=protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/lib/protocol.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const f = 1, c = "reachy-mini";
|
|
2
|
+
function d(e) {
|
|
3
|
+
if (!e || typeof e != "object") return !1;
|
|
4
|
+
const n = e;
|
|
5
|
+
return n.source === c && typeof n.type == "string" && n.version === 1;
|
|
6
|
+
}
|
|
7
|
+
function u(e) {
|
|
8
|
+
const n = JSON.stringify(e), o = s(n);
|
|
9
|
+
return `creds=${encodeURIComponent(o)}`;
|
|
10
|
+
}
|
|
11
|
+
function a(e) {
|
|
12
|
+
if (!e) return null;
|
|
13
|
+
const n = e.startsWith("#") ? e.slice(1) : e;
|
|
14
|
+
for (const o of n.split("&"))
|
|
15
|
+
if (o.startsWith("creds="))
|
|
16
|
+
try {
|
|
17
|
+
const t = decodeURIComponent(o.slice(6)), r = i(t);
|
|
18
|
+
return JSON.parse(r);
|
|
19
|
+
} catch (t) {
|
|
20
|
+
return console.warn("[reachy-mini-sdk/host] failed to decode creds hash", t), null;
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
function s(e) {
|
|
25
|
+
if (typeof window < "u" && typeof window.btoa == "function") {
|
|
26
|
+
const n = new TextEncoder().encode(e);
|
|
27
|
+
let o = "";
|
|
28
|
+
for (const t of n) o += String.fromCharCode(t);
|
|
29
|
+
return window.btoa(o);
|
|
30
|
+
}
|
|
31
|
+
return Buffer.from(e, "utf8").toString("base64");
|
|
32
|
+
}
|
|
33
|
+
function i(e) {
|
|
34
|
+
if (typeof window < "u" && typeof window.atob == "function") {
|
|
35
|
+
const n = window.atob(e), o = new Uint8Array(n.length);
|
|
36
|
+
for (let t = 0; t < n.length; t++) o[t] = n.charCodeAt(t);
|
|
37
|
+
return new TextDecoder().decode(o);
|
|
38
|
+
}
|
|
39
|
+
return Buffer.from(e, "base64").toString("utf8");
|
|
40
|
+
}
|
|
41
|
+
export {
|
|
42
|
+
c as PROTOCOL_SOURCE,
|
|
43
|
+
f as PROTOCOL_VERSION,
|
|
44
|
+
a as decodeCredsFromHash,
|
|
45
|
+
u as encodeCredsToHash,
|
|
46
|
+
d as isProtocolMessage
|
|
47
|
+
};
|
|
48
|
+
//# sourceMappingURL=protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.js","sources":["../../src/lib/protocol.ts"],"sourcesContent":["/**\n * postMessage protocol v1 between the host shell (parent window,\n * exposed via `@pollen-robotics/reachy-mini-sdk/host`) and an\n * embedded Reachy Mini app (iframe).\n *\n * Canonical reference: SPEC.md §6.\n *\n * Contract identity\n * ─────────────────\n * - Every message carries `version: 1`. Bumping that integer is\n * the ONLY way to introduce a breaking change to the wire\n * protocol. Additive changes (new optional fields, new typed\n * messages) ship without a version bump.\n * - Every message carries `source: 'reachy-mini'`. Lets receivers\n * distinguish our envelopes from unrelated `postMessage` traffic\n * (DevTools, MUI portals, browser extensions, ...).\n * - Both sides validate `event.origin` against the expected origin\n * before trusting the payload. In our deployment (same-origin\n * iframe within an HF Space) the expected origin is\n * `event.origin === window.location.origin`.\n *\n * Message families\n * ────────────────\n * 1. Lifecycle: boot / ready / leaving. Drive the visible state\n * of the host shell.\n * 2. State: `embed:app-state` lets the host render accurate\n * connection / wake-up overlays.\n * 3. Config & theme: opaque `config` payload + theme push.\n * 4. Error: `embed:error` for tear-down on app failure.\n *\n * Intentionally NOT in v1 (see SPEC §6 and §11):\n * - No `host:custom` / `embed:custom` free-form channel.\n * - No `embed:request-config-update` (apps don't push config).\n * - No heartbeat / ping-pong (backlog).\n */\n\n/** Protocol version. Bump on breaking changes. */\nexport const PROTOCOL_VERSION = 1;\n\n/** Source tag attached to every envelope. */\nexport const PROTOCOL_SOURCE = 'reachy-mini' as const;\n\n/** Theme mode the host applies to the embedded app. */\nexport type ThemeMode = 'dark' | 'light';\n\n/**\n * Connection lifecycle, as observed by the embedded app. Drives\n * the host's `ConnectingView` stepper and visibility of the\n * iframe.\n *\n * boot : app loaded, no SDK action yet\n * connecting : ReachyMini.connect() / startSession() in flight\n * live : session up, motors awake, app interactive\n * leaving : `host:leaving` received, app tearing down\n * error : non-recoverable failure\n */\nexport type AppPhase = 'boot' | 'connecting' | 'live' | 'leaving' | 'error';\n\n/**\n * Fine-grained step inside `connecting`. Maps to the 3 dots in\n * `StepsProgressIndicator`:\n * - `link` : waiting for `host:init` / `connect()` in flight\n * - `session` : `startSession()` in flight\n * - `wake` : `ensureAwake()` in flight\n *\n * Apps that don't differentiate can omit this field; the host\n * treats it as `link`.\n */\nexport type AppConnectingStep = 'link' | 'session' | 'wake';\n\n/** Opaque app-specific payload routed through the host. */\nexport type ConfigPayload = unknown;\n\n/** Reason supplied with `host:leaving` for logging. */\nexport type LeavingReason =\n | 'user-action'\n | 'session-stopped'\n | 'error'\n | 'pagehide';\n\n/* ─────────────────── HOST → EMBED ─────────────────── */\n\n/**\n * First message sent by the host once the iframe has shouted\n * `embed:ready`. Carries the credentials + initial state the app\n * needs to bring a session up.\n *\n * Same-origin iframe: the SDK CDN script is imported with a\n * relative URL from `index.html`. We still pass `signalingUrl`\n * here so the host can swap centrals (staging / self-hosted)\n * without a rebuild of the app.\n */\nexport interface HostInitMsg {\n source: typeof PROTOCOL_SOURCE;\n type: 'host:init';\n version: 1;\n theme: ThemeMode;\n signalingUrl: string;\n /** HF OAuth bearer token. Optional for apps that don't need to\n * authenticate (rare). */\n hfToken?: string;\n /** HF account user name, when known. */\n userName?: string | null;\n /** Robot ID selected by the host's picker. */\n robotPeerId: string;\n /** Optional opaque payload from `?config=<base64>` or from the\n * mobile-app handoff. App is responsible for parsing /\n * validating. */\n config: ConfigPayload;\n /** Host display name (e.g. \"Reachy Mini\") - useful for the\n * embed if it wants to surface \"Connected via …\" copy. */\n hostName: string;\n /** Embedded app's display name (passed by the dispatcher). */\n appName: string;\n}\n\n/** Theme changed live (user toggled OS palette, host UI switched). */\nexport interface HostThemeChangedMsg {\n source: typeof PROTOCOL_SOURCE;\n type: 'host:theme-changed';\n version: 1;\n theme: ThemeMode;\n}\n\n/** Config updated live without an iframe reload. */\nexport interface HostConfigChangedMsg {\n source: typeof PROTOCOL_SOURCE;\n type: 'host:config-changed';\n version: 1;\n config: ConfigPayload;\n}\n\n/**\n * Host is asking the app to wind down cleanly. The app SHOULD:\n * 1. Stop emitting motion commands.\n * 2. Disconnect any non-SDK resources (timers, listeners).\n * 3. Resolve every registered `onLeave` callback before the\n * `timeoutMs` deadline; otherwise the host force-unmounts\n * the iframe.\n *\n * The host does NOT expect an explicit `leave-ack`; resolving\n * `onLeave` callbacks within the deadline is sufficient.\n */\nexport interface HostLeavingMsg {\n source: typeof PROTOCOL_SOURCE;\n type: 'host:leaving';\n version: 1;\n reason: LeavingReason;\n /** Soft deadline in ms. After this the host unmounts the iframe\n * regardless. */\n timeoutMs: number;\n}\n\nexport type HostToEmbedMsg =\n | HostInitMsg\n | HostThemeChangedMsg\n | HostConfigChangedMsg\n | HostLeavingMsg;\n\n/* ─────────────────── EMBED → HOST ─────────────────── */\n\n/**\n * Emitted by the app as early as possible (synchronous tick of\n * the embed entry, before the SDK is touched). Tells the host\n * the iframe is alive and ready to receive `host:init`. The host\n * MUST NOT send `host:init` before seeing this.\n */\nexport interface EmbedReadyMsg {\n source: typeof PROTOCOL_SOURCE;\n type: 'embed:ready';\n version: 1;\n}\n\n/**\n * App-level connection state. The host renders its\n * `ConnectingView` overlay over the (still-mounted) iframe until\n * it sees `phase === 'live'`. Apps SHOULD emit this on every\n * transition; the host caches the last value and won't re-render\n * unless something changes.\n */\nexport interface EmbedAppStateMsg {\n source: typeof PROTOCOL_SOURCE;\n type: 'embed:app-state';\n version: 1;\n phase: AppPhase;\n /** Sub-step inside `connecting`. Ignored for other phases. */\n connectingStep?: AppConnectingStep | null;\n /** Optional human-readable hint shown in the overlay caption. */\n message?: string | null;\n}\n\n/** App requests to leave (user clicked an in-app exit, error,\n * ...). The host runs the same tear-down as a top-bar\n * \"End session\". */\nexport interface EmbedRequestLeaveMsg {\n source: typeof PROTOCOL_SOURCE;\n type: 'embed:request-leave';\n version: 1;\n}\n\n/** App-level error report. `fatal: true` switches the host to\n * ErrorView; `fatal: false` is logged and may surface a toast. */\nexport interface EmbedErrorMsg {\n source: typeof PROTOCOL_SOURCE;\n type: 'embed:error';\n version: 1;\n message: string;\n fatal: boolean;\n detail?: unknown;\n}\n\nexport type EmbedToHostMsg =\n | EmbedReadyMsg\n | EmbedAppStateMsg\n | EmbedRequestLeaveMsg\n | EmbedErrorMsg;\n\n/* ─────────────────── CREDS BUNDLE ─────────────────── */\n\n/**\n * Serialised credentials passed from the host (Mode A) or the\n * mobile app (Mode B) to the embed via the URL hash fragment.\n *\n * Hash-only (never URL search): browsers don't send the hash to\n * any server, so the HF token never appears in access logs,\n * referer headers, or HF Spaces frontend logs.\n *\n * The embed wipes the hash with `history.replaceState` on its\n * first synchronous tick, before any `await`, then proceeds with\n * the rest of the boot using the in-memory bundle.\n */\nexport interface CredsBundle {\n hfToken?: string | null;\n userName?: string | null;\n robotPeerId: string;\n signalingUrl: string;\n theme: ThemeMode;\n config: ConfigPayload;\n hostName: string;\n appName: string;\n}\n\n/* ─────────────────── HELPERS ─────────────────── */\n\n/**\n * Cheap discriminator: does this `unknown` look like a v1\n * envelope from our protocol? Receivers call this before\n * narrowing on `type`. Conservative on the version: an unknown\n * version is treated as \"not our protocol\" so the receiver\n * silently ignores it (forward-compat for a future v2 peer).\n */\nexport function isProtocolMessage(\n value: unknown,\n): value is { source: typeof PROTOCOL_SOURCE; type: string; version: number } {\n if (!value || typeof value !== 'object') return false;\n const record = value as Record<string, unknown>;\n return (\n record.source === PROTOCOL_SOURCE &&\n typeof record.type === 'string' &&\n record.version === PROTOCOL_VERSION\n );\n}\n\n/**\n * Encode a creds bundle to the URL hash fragment.\n * URL-safe base64 wrapper around JSON to avoid percent-encoding\n * noise.\n */\nexport function encodeCredsToHash(bundle: CredsBundle): string {\n const json = JSON.stringify(bundle);\n const b64 = encodeBase64Utf8(json);\n return `creds=${encodeURIComponent(b64)}`;\n}\n\n/**\n * Decode the creds bundle from a URL hash fragment. Returns\n * `null` if no `creds=` segment is present or if the payload is\n * malformed (caller handles the error by rendering ErrorView).\n */\nexport function decodeCredsFromHash(hash: string | null): CredsBundle | null {\n if (!hash) return null;\n const raw = hash.startsWith('#') ? hash.slice(1) : hash;\n for (const segment of raw.split('&')) {\n if (!segment.startsWith('creds=')) continue;\n try {\n const b64 = decodeURIComponent(segment.slice('creds='.length));\n const json = decodeBase64Utf8(b64);\n return JSON.parse(json) as CredsBundle;\n } catch (err) {\n console.warn('[reachy-mini-sdk/host] failed to decode creds hash', err);\n return null;\n }\n }\n return null;\n}\n\n/* UTF-8 safe base64 helpers (btoa / atob choke on non-Latin\n * characters in `config` payloads). Works in browsers and Node\n * (for unit tests). */\n\nfunction encodeBase64Utf8(input: string): string {\n if (typeof window !== 'undefined' && typeof window.btoa === 'function') {\n const bytes = new TextEncoder().encode(input);\n let bin = '';\n for (const b of bytes) bin += String.fromCharCode(b);\n return window.btoa(bin);\n }\n // Node fallback (tests / SSR).\n return Buffer.from(input, 'utf8').toString('base64');\n}\n\nfunction decodeBase64Utf8(input: string): string {\n if (typeof window !== 'undefined' && typeof window.atob === 'function') {\n const bin = window.atob(input);\n const bytes = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);\n return new TextDecoder().decode(bytes);\n }\n return Buffer.from(input, 'base64').toString('utf8');\n}\n"],"names":["PROTOCOL_VERSION","PROTOCOL_SOURCE","isProtocolMessage","value","record","encodeCredsToHash","bundle","json","b64","encodeBase64Utf8","decodeCredsFromHash","hash","raw","segment","decodeBase64Utf8","err","input","bytes","bin","b","i"],"mappings":"AAqCO,MAAMA,IAAmB,GAGnBC,IAAkB;AAmNxB,SAASC,EACdC,GAC4E;AAC5E,MAAI,CAACA,KAAS,OAAOA,KAAU,SAAU,QAAO;AAChD,QAAMC,IAASD;AACf,SACEC,EAAO,WAAWH,KAClB,OAAOG,EAAO,QAAS,YACvBA,EAAO,YAAY;AAEvB;AAOO,SAASC,EAAkBC,GAA6B;AAC7D,QAAMC,IAAO,KAAK,UAAUD,CAAM,GAC5BE,IAAMC,EAAiBF,CAAI;AACjC,SAAO,SAAS,mBAAmBC,CAAG,CAAC;AACzC;AAOO,SAASE,EAAoBC,GAAyC;AAC3E,MAAI,CAACA,EAAM,QAAO;AAClB,QAAMC,IAAMD,EAAK,WAAW,GAAG,IAAIA,EAAK,MAAM,CAAC,IAAIA;AACnD,aAAWE,KAAWD,EAAI,MAAM,GAAG;AACjC,QAAKC,EAAQ,WAAW,QAAQ;AAChC,UAAI;AACF,cAAML,IAAM,mBAAmBK,EAAQ,MAAM,CAAe,CAAC,GACvDN,IAAOO,EAAiBN,CAAG;AACjC,eAAO,KAAK,MAAMD,CAAI;AAAA,MACxB,SAASQ,GAAK;AACZ,uBAAQ,KAAK,sDAAsDA,CAAG,GAC/D;AAAA,MACT;AAEF,SAAO;AACT;AAMA,SAASN,EAAiBO,GAAuB;AAC/C,MAAI,OAAO,SAAW,OAAe,OAAO,OAAO,QAAS,YAAY;AACtE,UAAMC,IAAQ,IAAI,cAAc,OAAOD,CAAK;AAC5C,QAAIE,IAAM;AACV,eAAWC,KAAKF,EAAO,CAAAC,KAAO,OAAO,aAAaC,CAAC;AACnD,WAAO,OAAO,KAAKD,CAAG;AAAA,EACxB;AAEA,SAAO,OAAO,KAAKF,GAAO,MAAM,EAAE,SAAS,QAAQ;AACrD;AAEA,SAASF,EAAiBE,GAAuB;AAC/C,MAAI,OAAO,SAAW,OAAe,OAAO,OAAO,QAAS,YAAY;AACtE,UAAME,IAAM,OAAO,KAAKF,CAAK,GACvBC,IAAQ,IAAI,WAAWC,EAAI,MAAM;AACvC,aAASE,IAAI,GAAGA,IAAIF,EAAI,QAAQE,IAAK,CAAAH,EAAMG,CAAC,IAAIF,EAAI,WAAWE,CAAC;AAChE,WAAO,IAAI,YAAA,EAAc,OAAOH,CAAK;AAAA,EACvC;AACA,SAAO,OAAO,KAAKD,GAAO,QAAQ,EAAE,SAAS,MAAM;AACrD;"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-export of the SDK type surface, sourced from the canonical
|
|
3
|
+
* declarations next to the SDK runtime
|
|
4
|
+
* (`../../../reachy-mini-sdk.d.ts`). Kept as a thin barrel so existing
|
|
5
|
+
* `import type ... from '../lib/sdk-types'` call sites in the host
|
|
6
|
+
* code (`useSdk`, `useRobots`, `embed/index`, picker view, etc.)
|
|
7
|
+
* don't need to reach across the package layout themselves.
|
|
8
|
+
*
|
|
9
|
+
* If you're touching the type surface, edit the canonical file at
|
|
10
|
+
* `js/reachy-mini-sdk.d.ts` — it ships in the npm tarball as
|
|
11
|
+
* the companion `.d.ts` next to `reachy-mini-sdk.js`.
|
|
12
|
+
*/
|
|
13
|
+
export type { RobotInfo, RobotState, ReachyMiniOptions, ReachyMiniInstance, ReachyMiniConstructor, AutoConnectOptions, AutoConnectRobotChoice, AutoConnectResult, MotionAwaitOptions, SubscribeLogsOptions, } from '../../../reachy-mini-sdk';
|
|
14
|
+
import type { ReachyMiniConstructor } from '../../../reachy-mini-sdk';
|
|
15
|
+
/**
|
|
16
|
+
* Host-internal global augmentation: the host shell instantiates
|
|
17
|
+
* `new window.ReachyMini(opts)` (see `useSdk`, `embed/index.ts`)
|
|
18
|
+
* after either the npm entry points or the CDN bundles have
|
|
19
|
+
* auto-assigned the constructor at module-load time. We declare
|
|
20
|
+
* the global as non-optional here so those use sites stay
|
|
21
|
+
* non-null-asserted on the type level; the runtime guarantee is
|
|
22
|
+
* upheld by the side-effect imports in `entry/auto.ts`,
|
|
23
|
+
* `entry/embed.ts` and `index.ts`.
|
|
24
|
+
*
|
|
25
|
+
* The SDK package's companion `reachy-mini-sdk.d.ts` deliberately
|
|
26
|
+
* does NOT augment `Window.ReachyMini` so external consumers who
|
|
27
|
+
* import only the SDK don't have a phantom non-optional global
|
|
28
|
+
* appear in their type-checker.
|
|
29
|
+
*/
|
|
30
|
+
declare global {
|
|
31
|
+
interface Window {
|
|
32
|
+
ReachyMini: ReachyMiniConstructor;
|
|
33
|
+
/** Injected by HF Spaces at container start when
|
|
34
|
+
* `hf_oauth: true` is set in the Space frontmatter. */
|
|
35
|
+
huggingface?: {
|
|
36
|
+
variables?: {
|
|
37
|
+
OAUTH_CLIENT_ID?: string;
|
|
38
|
+
OAUTH_SCOPES?: string;
|
|
39
|
+
SPACE_HOST?: string;
|
|
40
|
+
SPACE_ID?: string;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export {};
|
|
46
|
+
//# sourceMappingURL=sdk-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sdk-types.d.ts","sourceRoot":"","sources":["../../src/lib/sdk-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,YAAY,EACV,SAAS,EACT,UAAU,EACV,iBAAiB,EACjB,kBAAkB,EAClB,qBAAqB,EACrB,kBAAkB,EAClB,sBAAsB,EACtB,iBAAiB,EACjB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAEtE;;;;;;;;;;;;;;GAcG;AACH,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,UAAU,EAAE,qBAAqB,CAAC;QAClC;gEACwD;QACxD,WAAW,CAAC,EAAE;YACZ,SAAS,CAAC,EAAE;gBACV,eAAe,CAAC,EAAE,MAAM,CAAC;gBACzB,YAAY,CAAC,EAAE,MAAM,CAAC;gBACtB,UAAU,CAAC,EAAE,MAAM,CAAC;gBACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;aACnB,CAAC;SACH,CAAC;KACH;CACF;AAED,OAAO,EAAE,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings + auth helpers for the host shell.
|
|
3
|
+
*
|
|
4
|
+
* Three concerns live here:
|
|
5
|
+
* 1. OAuth client ID resolution (HF Spaces injection > caller-
|
|
6
|
+
* supplied > `localStorage`).
|
|
7
|
+
* 2. Dev token seeding: skips the OAuth dance entirely when the
|
|
8
|
+
* app author passes `devToken` to `mountHost()`.
|
|
9
|
+
* 3. Inter-load flags (`oauth-pending` for the "welcome back"
|
|
10
|
+
* animation; `signed-out` so we don't auto-relogin after a
|
|
11
|
+
* user-initiated logout).
|
|
12
|
+
*
|
|
13
|
+
* Storage rules (SPEC §8.2):
|
|
14
|
+
* - HF token + user name live in `sessionStorage` (tab-scoped,
|
|
15
|
+
* wiped on close). The Reachy Mini SDK reads them from
|
|
16
|
+
* `hf_token` / `hf_username` / `hf_token_expires`.
|
|
17
|
+
* - Persistent flags (`hf_oauth_client_id`, `oauth-pending`,
|
|
18
|
+
* `signed-out`) live in `localStorage`.
|
|
19
|
+
* - Tokens have a 15 min TTL by default; the SDK refreshes
|
|
20
|
+
* on demand if a real OAuth refresh flow is wired.
|
|
21
|
+
*/
|
|
22
|
+
export type ClientIdSource = 'none' | 'caller' | 'space' | 'localStorage';
|
|
23
|
+
export interface DevTokenConfig {
|
|
24
|
+
token: string;
|
|
25
|
+
userName: string;
|
|
26
|
+
}
|
|
27
|
+
/** Resolve the OAuth client ID from all known sources. */
|
|
28
|
+
export declare function resolveClientId(callerSupplied?: string): string | undefined;
|
|
29
|
+
/** Same as `resolveClientId` but reports which source won.
|
|
30
|
+
* Useful for the SignInView's "you're using…" hint. */
|
|
31
|
+
export declare function resolveClientIdSource(callerSupplied?: string): {
|
|
32
|
+
clientId: string | undefined;
|
|
33
|
+
source: ClientIdSource;
|
|
34
|
+
};
|
|
35
|
+
/** Persist an OAuth client ID for future sessions. */
|
|
36
|
+
export declare function storeClientId(clientId: string): void;
|
|
37
|
+
/** Pre-seed the SDK's auth cache with a personal access token.
|
|
38
|
+
* Used in dev to skip the OAuth redirect. */
|
|
39
|
+
export declare function seedDevToken(config: DevTokenConfig): void;
|
|
40
|
+
/** `true` if a dev token was passed to `mountHost()` this page
|
|
41
|
+
* load, regardless of whether the session storage was later
|
|
42
|
+
* wiped by a sign-out. */
|
|
43
|
+
export declare function hasCachedDevToken(): boolean;
|
|
44
|
+
/** Re-seed the session storage from the cached dev token. No-op
|
|
45
|
+
* if no dev token was provided to `mountHost()`. Returns `true`
|
|
46
|
+
* iff a token was re-seeded. */
|
|
47
|
+
export declare function rehydrateDevToken(): boolean;
|
|
48
|
+
/** Mark that we're about to start an OAuth redirect. The host
|
|
49
|
+
* reads this on the next boot to show the "welcome back" view. */
|
|
50
|
+
export declare function markOAuthPending(): void;
|
|
51
|
+
/** Read and clear the `oauth-pending` flag in one call. Returns
|
|
52
|
+
* `true` iff this boot is the result of an OAuth redirect. */
|
|
53
|
+
export declare function consumeOAuthPending(): boolean;
|
|
54
|
+
/** Mark the user as explicitly signed out so the SDK doesn't
|
|
55
|
+
* silently re-authenticate from a leftover session cookie. */
|
|
56
|
+
export declare function markUserSignedOut(): void;
|
|
57
|
+
/** Clear the signed-out flag (called when the user clicks
|
|
58
|
+
* "Sign in" after a logout). */
|
|
59
|
+
export declare function clearSignedOutFlag(): void;
|
|
60
|
+
/** Returns `true` if the user explicitly signed out earlier. */
|
|
61
|
+
export declare function isUserSignedOut(): boolean;
|
|
62
|
+
/** Wipe HF token keys from sessionStorage. Called by the embed
|
|
63
|
+
* on `host:leaving` before the iframe unmounts (SPEC §8.2). */
|
|
64
|
+
export declare function wipeHfSessionStorage(): void;
|
|
65
|
+
/** Decode a base64 JSON `?config=` URL parameter into a typed
|
|
66
|
+
* value. Returns `null` on missing / malformed input. The
|
|
67
|
+
* caller is responsible for validating the shape. */
|
|
68
|
+
export declare function readUrlConfig<T = unknown>(): T | null;
|
|
69
|
+
//# sourceMappingURL=settings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/lib/settings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAYH,MAAM,MAAM,cAAc,GACtB,MAAM,GACN,QAAQ,GACR,OAAO,GACP,cAAc,CAAC;AAEnB,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAID,0DAA0D;AAC1D,wBAAgB,eAAe,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAW3E;AAED;wDACwD;AACxD,wBAAgB,qBAAqB,CACnC,cAAc,CAAC,EAAE,MAAM,GACtB;IAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,MAAM,EAAE,cAAc,CAAA;CAAE,CAW1D;AAED,sDAAsD;AACtD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAMpD;AAWD;8CAC8C;AAC9C,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAYzD;AAED;;2BAE2B;AAC3B,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED;;iCAEiC;AACjC,wBAAgB,iBAAiB,IAAI,OAAO,CAI3C;AAID;mEACmE;AACnE,wBAAgB,gBAAgB,IAAI,IAAI,CAMvC;AAED;+DAC+D;AAC/D,wBAAgB,mBAAmB,IAAI,OAAO,CAQ7C;AAID;+DAC+D;AAC/D,wBAAgB,iBAAiB,IAAI,IAAI,CAMxC;AAED;iCACiC;AACjC,wBAAgB,kBAAkB,IAAI,IAAI,CAMzC;AAED,gEAAgE;AAChE,wBAAgB,eAAe,IAAI,OAAO,CAMzC;AAID;gEACgE;AAChE,wBAAgB,oBAAoB,IAAI,IAAI,CAQ3C;AAID;;sDAEsD;AACtD,wBAAgB,aAAa,CAAC,CAAC,GAAG,OAAO,KAAK,CAAC,GAAG,IAAI,CAUrD"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the signaling URL for the ReachyMini SDK.
|
|
3
|
+
*
|
|
4
|
+
* Priority:
|
|
5
|
+
* 1. `?signaling_url=<url>` URL parameter (override for
|
|
6
|
+
* staging / self-hosted central).
|
|
7
|
+
* 2. `window.huggingface.variables.SIGNALING_URL` if injected
|
|
8
|
+
* by the HF Spaces frontmatter (rare).
|
|
9
|
+
* 3. The canonical Pollen-Robotics central
|
|
10
|
+
* (`https://pollen-robotics-reachy-mini-central.hf.space`).
|
|
11
|
+
*
|
|
12
|
+
* Why we don't fall back to the SDK's bundled default
|
|
13
|
+
* ───────────────────────────────────────────────────
|
|
14
|
+
* The SDK shipped with `tfrere-reachy-mini-central.hf.space`
|
|
15
|
+
* baked in for legacy reasons. Reachies registered through the
|
|
16
|
+
* mobile app (the canonical onboarding path) sit on the
|
|
17
|
+
* `pollen-robotics-...` Space, so a host that lets the SDK fall
|
|
18
|
+
* back to the legacy URL ends up showing an empty list even when
|
|
19
|
+
* the user owns one of those robots. We override the default
|
|
20
|
+
* here so the host and the mobile app talk to the same central
|
|
21
|
+
* out of the box.
|
|
22
|
+
*
|
|
23
|
+
* Never throws. A malformed override just falls back to the
|
|
24
|
+
* canonical default.
|
|
25
|
+
*/
|
|
26
|
+
export declare const DEFAULT_CENTRAL_SIGNALING_URL = "https://pollen-robotics-reachy-mini-central.hf.space";
|
|
27
|
+
export declare function resolveSignalingUrl(): string;
|
|
28
|
+
//# sourceMappingURL=signalingUrl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signalingUrl.d.ts","sourceRoot":"","sources":["../../src/lib/signalingUrl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,eAAO,MAAM,6BAA6B,yDACc,CAAC;AAEzD,wBAAgB,mBAAmB,IAAI,MAAM,CAsB5C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../src/lib/theme.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AA2FlD,eAAO,MAAM,UAAU,EAAE,KAA2B,CAAC;AACrD,eAAO,MAAM,SAAS,EAAE,KAA0B,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ThemeMode } from './protocol';
|
|
2
|
+
/**
|
|
3
|
+
* React hook returning the current theme mode and reacting to OS
|
|
4
|
+
* palette changes. Stable across React StrictMode double-mounts:
|
|
5
|
+
* the `matchMedia` listener is registered in an effect with a
|
|
6
|
+
* proper cleanup.
|
|
7
|
+
*/
|
|
8
|
+
export declare function useThemeMode(): ThemeMode;
|
|
9
|
+
//# sourceMappingURL=themeMode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"themeMode.d.ts","sourceRoot":"","sources":["../../src/lib/themeMode.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AA6B5C;;;;;GAKG;AACH,wBAAgB,YAAY,IAAI,SAAS,CAqBxC"}
|