@pollen-robotics/reachy-mini-sdk 1.10.0-dev.0.main.b50d97e → 1.10.0-dev.0.main.c8367cd
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 +202 -88
- package/dist/lib/reachy-mini.d.ts.map +1 -1
- package/dist/lib/reachy-mini.js +651 -566
- 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 +280 -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-uXNMidlL.js +2315 -0
- package/host/dist/chunks/index-Beu4GNzi.js +821 -0
- package/host/dist/chunks/{mountHost-DjE5zE9R.js → mountHost-BTTF4Uu9.js} +24933 -27488
- package/host/dist/components/ConnectingView.d.ts.map +1 -1
- 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/LeavingView.d.ts.map +1 -1
- 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/components/WelcomeBackOverlay.d.ts.map +1 -1
- package/host/dist/embed/index.d.ts +68 -5
- package/host/dist/embed/index.d.ts.map +1 -1
- package/host/dist/entry/auto.js +2 -2
- 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/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.d.ts +2 -2
- package/host/dist/index.d.ts.map +1 -1
- package/host/dist/index.js +11 -10
- 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 +11 -4
- package/host/dist/chunks/index-lMs2sCXG.js +0 -439
- package/host/dist/chunks/reachy-mini-CdsdQLIU.js +0 -1523
- package/host/dist/lib/centralListener.d.ts +0 -73
- package/host/dist/lib/centralListener.d.ts.map +0 -1
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session resilience supervisor for the ReachyMini SDK.
|
|
3
|
+
*
|
|
4
|
+
* Owns the three transport-resilience mechanisms so the main class only
|
|
5
|
+
* has to forward browser/pc events and expose narrow accessors:
|
|
6
|
+
*
|
|
7
|
+
* 1. ICE-blip debounce + network awareness — grace windows around
|
|
8
|
+
* `iceConnectionState` transitions (visibility-deferred while the
|
|
9
|
+
* tab is hidden) and `online`/`offline`/`connection.change`
|
|
10
|
+
* forwarding as public events.
|
|
11
|
+
* 2. Automatic session re-dial — the daemon's `webrtcbin` cannot do a
|
|
12
|
+
* standards ICE restart (missing upstream support), so the recovery
|
|
13
|
+
* unit is the whole SESSION: tear down the dead RTCPeerConnection
|
|
14
|
+
* and dial the same robot again through central. Runs inside the
|
|
15
|
+
* SDK so every consumer (host embed, mobile app, third-party apps)
|
|
16
|
+
* inherits it.
|
|
17
|
+
* 3. Data-channel silence watchdog — application-level heartbeat
|
|
18
|
+
* without any daemon change: a streaming session already receives
|
|
19
|
+
* constant inbound traffic (get_state poll replies and/or pushed
|
|
20
|
+
* pose frames), so prolonged TOTAL silence is a dead transport that
|
|
21
|
+
* ICE hasn't noticed yet.
|
|
22
|
+
*
|
|
23
|
+
* The supervisor never touches the RTCPeerConnection, data channel or
|
|
24
|
+
* pending promises directly: every side effect goes through
|
|
25
|
+
* `SessionSupervisorDeps`, implemented by `ReachyMini` as closures over
|
|
26
|
+
* its own private state. That keeps the ownership boundary honest — the
|
|
27
|
+
* class owns the transport, the supervisor owns the RECOVERY POLICY.
|
|
28
|
+
*/
|
|
29
|
+
import { createLogger } from './logger.js';
|
|
30
|
+
const log = createLogger('session');
|
|
31
|
+
/* ─── Tunables ─────────────────────────────────────────────────────── */
|
|
32
|
+
// ICE grace windows. `disconnected` is transient per spec (browsers
|
|
33
|
+
// usually heal it in 1-2 s on WiFi blips / AP roams), so debounce it.
|
|
34
|
+
// `failed` IS terminal per spec, but we've observed real
|
|
35
|
+
// `failed → connected` flips on rapid AP roams and iOS BT route
|
|
36
|
+
// changes — 1 s of debounce absorbs those without noticeably delaying
|
|
37
|
+
// a real failure.
|
|
38
|
+
const ICE_DISCONNECT_GRACE_MS = 3000;
|
|
39
|
+
const ICE_FAILED_GRACE_MS = 1000;
|
|
40
|
+
/**
|
|
41
|
+
* Ceiling on how long `armIceGraceOnVisibility` waits for a hidden tab
|
|
42
|
+
* to come back. The daemon's `webrtcsink` runs a STUN consent-freshness
|
|
43
|
+
* check (RFC 7675, ~30 s default) and unilaterally tears its side of
|
|
44
|
+
* the session down past that window, releasing the producer slot on
|
|
45
|
+
* central — so after a long background stint the transport is gone and
|
|
46
|
+
* another foreground grace would be a lie. 60 s gives a 2× margin over
|
|
47
|
+
* the daemon-side timeout: long enough to absorb "phone in pocket for
|
|
48
|
+
* 45 s", short enough to be honest with the user.
|
|
49
|
+
*/
|
|
50
|
+
const MAX_VISIBILITY_DEFER_MS = 60_000;
|
|
51
|
+
// Re-dial backoff (one entry per attempt, in ms of wait BEFORE that
|
|
52
|
+
// attempt): ~22 s window + dial time. Each attempt is capped because
|
|
53
|
+
// past ~15 s the robot side likely still holds the previous (dead)
|
|
54
|
+
// session — tear the attempt down and retry against a freed slot.
|
|
55
|
+
const REDIAL_BACKOFF_MS = [0, 2000, 4000, 8000, 8000];
|
|
56
|
+
const REDIAL_DIAL_TIMEOUT_MS = 15_000;
|
|
57
|
+
// Data-channel silence watchdog. A streaming session receives constant
|
|
58
|
+
// inbound traffic (`get_state` replies every ≤500 ms and/or ~30 Hz pose
|
|
59
|
+
// frames), so prolonged TOTAL silence is a dead transport even while
|
|
60
|
+
// ICE still reports 'connected' (half-open links dodge STUN consent
|
|
61
|
+
// checks for tens of seconds). Past the nudge threshold send one extra
|
|
62
|
+
// `get_state`; past the fatal threshold escalate — still ~4× faster
|
|
63
|
+
// than the RFC 7675 consent-freshness teardown (~30 s). Hidden /
|
|
64
|
+
// throttled tabs re-baseline instead of judging stale data.
|
|
65
|
+
const DC_WATCHDOG_TICK_MS = 1000;
|
|
66
|
+
const DC_SILENCE_NUDGE_MS = 2500;
|
|
67
|
+
const DC_SILENCE_FATAL_MS = 8000;
|
|
68
|
+
/* ─── Supervisor ───────────────────────────────────────────────────── */
|
|
69
|
+
export class SessionSupervisor {
|
|
70
|
+
_deps;
|
|
71
|
+
// ICE-blip debounce. All handler slots are scoped to the lifetime
|
|
72
|
+
// of a live session (armed from the pc's state handler, cleared by
|
|
73
|
+
// every teardown path via `clearIceGrace`).
|
|
74
|
+
_iceGraceTimer = null;
|
|
75
|
+
_iceGraceReason = null;
|
|
76
|
+
_pendingVisibilityHandler = null;
|
|
77
|
+
// Network awareness (installed in startSession, removed on teardown).
|
|
78
|
+
_onlineHandler = null;
|
|
79
|
+
_offlineHandler = null;
|
|
80
|
+
_connectionChangeHandler = null;
|
|
81
|
+
// Automatic re-dial. Mutable via `setAutoReconnect()` so flows that
|
|
82
|
+
// EXPECT a transport death (the daemon self-update reboot) can stand
|
|
83
|
+
// the machinery down.
|
|
84
|
+
_autoReconnect;
|
|
85
|
+
_redialing = false;
|
|
86
|
+
_redialWake = null;
|
|
87
|
+
// Data-channel silence watchdog.
|
|
88
|
+
_dcWatchdogId = null;
|
|
89
|
+
_lastDcInboundAt = 0;
|
|
90
|
+
_dcWatchdogLastTickAt = 0;
|
|
91
|
+
_dcSilenceNudged = false;
|
|
92
|
+
constructor(deps, options) {
|
|
93
|
+
this._deps = deps;
|
|
94
|
+
this._autoReconnect = options.autoReconnect;
|
|
95
|
+
}
|
|
96
|
+
/** True while the re-dial loop is running. */
|
|
97
|
+
get redialing() {
|
|
98
|
+
return this._redialing;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Enable/disable the automatic session re-dial at runtime.
|
|
102
|
+
* Disabling also aborts any re-dial already in flight.
|
|
103
|
+
*/
|
|
104
|
+
setAutoReconnect(enabled) {
|
|
105
|
+
this._autoReconnect = enabled;
|
|
106
|
+
if (!enabled)
|
|
107
|
+
this.cancelRedial();
|
|
108
|
+
}
|
|
109
|
+
/** Fatal errors carry the classic webrtc `error` payload shape. */
|
|
110
|
+
_emitError(error) {
|
|
111
|
+
this._deps.emit('error', { source: 'webrtc', error });
|
|
112
|
+
}
|
|
113
|
+
/* ─── ICE-blip debounce + network awareness ──────────────────────
|
|
114
|
+
*
|
|
115
|
+
* Both halves below are intentionally generic (they don't know
|
|
116
|
+
* about motion, audio, or the FSM): they just smooth out
|
|
117
|
+
* browser-level events so the consumer's own state machine doesn't
|
|
118
|
+
* get torn down by routine WiFi/4G/screen-off noise.
|
|
119
|
+
*/
|
|
120
|
+
/** ICE healed back to `connected`/`completed`. */
|
|
121
|
+
onIceHealed() {
|
|
122
|
+
this.clearIceGrace();
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* ICE hit `disconnected` — TRANSIENT per spec, debounce before
|
|
126
|
+
* escalating. If the tab is hidden, JS timers are throttled and
|
|
127
|
+
* would fire unpredictably late, so defer the grace window to the
|
|
128
|
+
* next foreground frame.
|
|
129
|
+
*/
|
|
130
|
+
onIceDisconnected() {
|
|
131
|
+
if (typeof document !== 'undefined' && document.hidden) {
|
|
132
|
+
this._armIceGraceOnVisibility();
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
this._scheduleIceGrace(ICE_DISCONNECT_GRACE_MS, 'disconnected');
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* ICE hit `failed` — terminal per spec, but in practice we've seen
|
|
140
|
+
* `failed → connected` on rapid AP roams / BT route changes on iOS.
|
|
141
|
+
* Give the ICE agent a short window to surprise us before rejecting
|
|
142
|
+
* the session.
|
|
143
|
+
*/
|
|
144
|
+
onIceFailed() {
|
|
145
|
+
this._scheduleIceGrace(ICE_FAILED_GRACE_MS, 'failed');
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Cancel any pending ICE grace timer and visibility handler. Called
|
|
149
|
+
* on a healed `connected`/`completed` transition AND from the
|
|
150
|
+
* lifecycle teardown paths so a callback can't fire after the pc
|
|
151
|
+
* is closed.
|
|
152
|
+
*/
|
|
153
|
+
clearIceGrace() {
|
|
154
|
+
if (this._iceGraceTimer !== null) {
|
|
155
|
+
clearTimeout(this._iceGraceTimer);
|
|
156
|
+
this._iceGraceTimer = null;
|
|
157
|
+
}
|
|
158
|
+
this._iceGraceReason = null;
|
|
159
|
+
if (this._pendingVisibilityHandler && typeof document !== 'undefined') {
|
|
160
|
+
document.removeEventListener('visibilitychange', this._pendingVisibilityHandler);
|
|
161
|
+
}
|
|
162
|
+
this._pendingVisibilityHandler = null;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Start a grace window. After `ms`, re-check the live ICE state:
|
|
166
|
+
* - If we healed back to `connected`/`completed`, the timer was
|
|
167
|
+
* already cancelled in the pc's state handler, so we never get
|
|
168
|
+
* here.
|
|
169
|
+
* - If we're still in the originally-observed bad state (or
|
|
170
|
+
* worse), surface the error and reject any pending session
|
|
171
|
+
* promise. The original code path is preserved verbatim so
|
|
172
|
+
* downstream consumers see the same `error` payload shape.
|
|
173
|
+
*/
|
|
174
|
+
_scheduleIceGrace(ms, reason) {
|
|
175
|
+
// Coalesce: if a grace is already pending and the reason hasn't
|
|
176
|
+
// changed, keep the original timer so a flurry of identical
|
|
177
|
+
// transitions doesn't reset the clock. If the reason changed
|
|
178
|
+
// (typically `disconnected` → `failed`, but also the reverse on
|
|
179
|
+
// some Android WebViews), replace the timer with the new
|
|
180
|
+
// (reason, ms) pair — the latest signal wins.
|
|
181
|
+
if (this._iceGraceTimer !== null) {
|
|
182
|
+
if (this._iceGraceReason === reason)
|
|
183
|
+
return;
|
|
184
|
+
clearTimeout(this._iceGraceTimer);
|
|
185
|
+
}
|
|
186
|
+
this._iceGraceReason = reason;
|
|
187
|
+
this._iceGraceTimer = setTimeout(() => {
|
|
188
|
+
this._iceGraceTimer = null;
|
|
189
|
+
const r = this._iceGraceReason;
|
|
190
|
+
this._iceGraceReason = null;
|
|
191
|
+
const s = this._deps.iceState();
|
|
192
|
+
if (s === 'connected' || s === 'completed')
|
|
193
|
+
return; // healed
|
|
194
|
+
if (r === 'disconnected' && s === 'disconnected') {
|
|
195
|
+
if (this.maybeBeginRedial(`ICE stuck in 'disconnected' for > ${ms}ms`))
|
|
196
|
+
return;
|
|
197
|
+
this._emitError(new Error(`ICE stuck in 'disconnected' for > ${ms}ms`));
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (r === 'failed' || s === 'failed') {
|
|
201
|
+
const err = new Error('ICE connection failed');
|
|
202
|
+
if (this._deps.rejectPendingSession(err)) {
|
|
203
|
+
// Mid-setup failure: keep the promise contract — the
|
|
204
|
+
// caller of startSession() owns the retry decision.
|
|
205
|
+
// When that caller is the redial loop itself, skip
|
|
206
|
+
// the fatal `error` emit: the loop retries and the
|
|
207
|
+
// app only sees `sessionReconnecting`.
|
|
208
|
+
if (!this._redialing)
|
|
209
|
+
this._emitError(err);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (this.maybeBeginRedial('ICE connection failed'))
|
|
213
|
+
return;
|
|
214
|
+
this._emitError(err);
|
|
215
|
+
}
|
|
216
|
+
}, ms);
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* `disconnected` while the tab is hidden. JS timers are throttled
|
|
220
|
+
* in background tabs (Chrome clamps to ~1 Hz, Safari can pause
|
|
221
|
+
* altogether), so a foreground grace timer would either miss the
|
|
222
|
+
* window or fire long after the connection healed. Wait for the
|
|
223
|
+
* tab to come back, then re-evaluate.
|
|
224
|
+
*/
|
|
225
|
+
_armIceGraceOnVisibility() {
|
|
226
|
+
if (this._pendingVisibilityHandler)
|
|
227
|
+
return;
|
|
228
|
+
const deferredAt = Date.now();
|
|
229
|
+
const handler = () => {
|
|
230
|
+
if (typeof document !== 'undefined' && document.hidden)
|
|
231
|
+
return;
|
|
232
|
+
document.removeEventListener('visibilitychange', handler);
|
|
233
|
+
this._pendingVisibilityHandler = null;
|
|
234
|
+
if (!this._deps.hasPc())
|
|
235
|
+
return;
|
|
236
|
+
const s = this._deps.iceState();
|
|
237
|
+
if (s === 'connected' || s === 'completed')
|
|
238
|
+
return; // healed in bg
|
|
239
|
+
// Ceiling: if the user backgrounded past the daemon's
|
|
240
|
+
// ICE-consent freshness window the session is gone from
|
|
241
|
+
// the daemon's side regardless of what the pc reports
|
|
242
|
+
// locally. Running another foreground grace would tell
|
|
243
|
+
// the user "Reconnecting…" for a recovery that can never
|
|
244
|
+
// happen. Escalate immediately so the host renders the
|
|
245
|
+
// real "session expired" UX. See MAX_VISIBILITY_DEFER_MS.
|
|
246
|
+
if (Date.now() - deferredAt > MAX_VISIBILITY_DEFER_MS) {
|
|
247
|
+
const err = new Error('Session expired while tab was backgrounded');
|
|
248
|
+
if (this._deps.rejectPendingSession(err)) {
|
|
249
|
+
if (!this._redialing)
|
|
250
|
+
this._emitError(err);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
// The transport is unrecoverable (daemon dropped its side
|
|
254
|
+
// past the consent-freshness window) but the user is BACK
|
|
255
|
+
// and looking at the app — the perfect moment to re-dial
|
|
256
|
+
// rather than render "session expired".
|
|
257
|
+
if (this.maybeBeginRedial('Session expired while tab was backgrounded'))
|
|
258
|
+
return;
|
|
259
|
+
this._emitError(err);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (s === 'failed') {
|
|
263
|
+
this._scheduleIceGrace(ICE_FAILED_GRACE_MS, 'failed');
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
// Still disconnected when we came back — give it a normal
|
|
267
|
+
// foreground grace window now that timers fire reliably.
|
|
268
|
+
this._scheduleIceGrace(ICE_DISCONNECT_GRACE_MS, 'disconnected');
|
|
269
|
+
};
|
|
270
|
+
document.addEventListener('visibilitychange', handler);
|
|
271
|
+
this._pendingVisibilityHandler = handler;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Install browser-level network listeners and forward them as
|
|
275
|
+
* public `networkOnline` / `networkOffline` / `networkChange`
|
|
276
|
+
* events on the instance. Idempotent: called from `startSession()`,
|
|
277
|
+
* removed by `uninstallNetworkListeners` on teardown. Reachable
|
|
278
|
+
* only when there's a live `window` (defensive guard for SSR /
|
|
279
|
+
* test environments).
|
|
280
|
+
*
|
|
281
|
+
* `online` / `offline` are semantically about CONNECTIVITY:
|
|
282
|
+
* "does the OS think we can reach the internet". They flip
|
|
283
|
+
* symmetrically.
|
|
284
|
+
*
|
|
285
|
+
* `connection.change` (NetworkInformation API, Chrome / Android
|
|
286
|
+
* WebView only) is semantically about the TRANSPORT: it fires
|
|
287
|
+
* on Wi-Fi → 4G swaps, AP roams, etc. without necessarily going
|
|
288
|
+
* through `offline`. We forward it as its own `networkChange`
|
|
289
|
+
* event rather than aliasing it onto `networkOnline`, so
|
|
290
|
+
* consumers don't have to guess whether they're seeing a real
|
|
291
|
+
* connectivity recovery or a silent transport swap.
|
|
292
|
+
*/
|
|
293
|
+
installNetworkListeners() {
|
|
294
|
+
if (this._onlineHandler || typeof window === 'undefined')
|
|
295
|
+
return;
|
|
296
|
+
const onOnline = () => this._deps.emit('networkOnline', {});
|
|
297
|
+
const onOffline = () => this._deps.emit('networkOffline', {});
|
|
298
|
+
window.addEventListener('online', onOnline);
|
|
299
|
+
window.addEventListener('offline', onOffline);
|
|
300
|
+
this._onlineHandler = onOnline;
|
|
301
|
+
this._offlineHandler = onOffline;
|
|
302
|
+
const conn = navigator.connection;
|
|
303
|
+
if (conn && typeof conn.addEventListener === 'function') {
|
|
304
|
+
const onChange = () => this._deps.emit('networkChange', {
|
|
305
|
+
effectiveType: conn.effectiveType,
|
|
306
|
+
downlink: conn.downlink,
|
|
307
|
+
rtt: conn.rtt,
|
|
308
|
+
saveData: conn.saveData,
|
|
309
|
+
});
|
|
310
|
+
conn.addEventListener('change', onChange);
|
|
311
|
+
this._connectionChangeHandler = onChange;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
/** Counterpart to `installNetworkListeners`. */
|
|
315
|
+
uninstallNetworkListeners() {
|
|
316
|
+
if (typeof window !== 'undefined') {
|
|
317
|
+
if (this._onlineHandler) {
|
|
318
|
+
window.removeEventListener('online', this._onlineHandler);
|
|
319
|
+
}
|
|
320
|
+
if (this._offlineHandler) {
|
|
321
|
+
window.removeEventListener('offline', this._offlineHandler);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
const conn = navigator.connection;
|
|
325
|
+
if (conn && this._connectionChangeHandler && typeof conn.removeEventListener === 'function') {
|
|
326
|
+
conn.removeEventListener('change', this._connectionChangeHandler);
|
|
327
|
+
}
|
|
328
|
+
this._onlineHandler = null;
|
|
329
|
+
this._offlineHandler = null;
|
|
330
|
+
this._connectionChangeHandler = null;
|
|
331
|
+
}
|
|
332
|
+
/* ─── Automatic session re-dial ─────────────────────────────────── */
|
|
333
|
+
/**
|
|
334
|
+
* Escalation funnel for dead-transport signals. Returns `true` when
|
|
335
|
+
* the failure is being handled by a re-dial (callers should then skip
|
|
336
|
+
* their fatal `error` emit). Only fires for an ESTABLISHED session:
|
|
337
|
+
* mid-setup failures keep rejecting the startSession() promise so the
|
|
338
|
+
* original caller stays in charge of retries.
|
|
339
|
+
*/
|
|
340
|
+
maybeBeginRedial(cause) {
|
|
341
|
+
if (!this._autoReconnect)
|
|
342
|
+
return false;
|
|
343
|
+
if (this._redialing)
|
|
344
|
+
return true; // already in progress
|
|
345
|
+
if (this._deps.isMidSetup())
|
|
346
|
+
return false;
|
|
347
|
+
const robotId = this._deps.selectedRobotId();
|
|
348
|
+
if (!robotId || !this._deps.hasPc())
|
|
349
|
+
return false;
|
|
350
|
+
this._redialing = true;
|
|
351
|
+
log.info(`auto-reconnect: ${cause} - re-dialing ${robotId}`);
|
|
352
|
+
void this._runRedialLoop(robotId, cause);
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* The re-dial loop. One teardown, then up to REDIAL_BACKOFF_MS.length
|
|
357
|
+
* attempts, each preceded by its backoff slot. Every attempt re-runs
|
|
358
|
+
* the normal startSession() flow (and connect() first if the SSE feed
|
|
359
|
+
* died with the network), so a success is indistinguishable from a
|
|
360
|
+
* fresh session: `streaming` re-fires, videoTrack re-attaches, the
|
|
361
|
+
* pose subscription is re-asserted by the session-ready path.
|
|
362
|
+
*
|
|
363
|
+
* Exits early — without emitting anything further — when the user
|
|
364
|
+
* calls stopSession()/disconnect() or dials another robot, all of
|
|
365
|
+
* which flip `_redialing` off via `cancelRedial`.
|
|
366
|
+
*/
|
|
367
|
+
async _runRedialLoop(robotId, cause) {
|
|
368
|
+
const maxAttempts = REDIAL_BACKOFF_MS.length;
|
|
369
|
+
this._deps.teardownForRedial();
|
|
370
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
371
|
+
if (!this._redialing)
|
|
372
|
+
return;
|
|
373
|
+
this._deps.emit('sessionReconnecting', { attempt, maxAttempts, cause });
|
|
374
|
+
await this._redialSleep(REDIAL_BACKOFF_MS[attempt - 1]);
|
|
375
|
+
if (!this._redialing)
|
|
376
|
+
return;
|
|
377
|
+
try {
|
|
378
|
+
if (this._deps.isSignalingDown()) {
|
|
379
|
+
// The network drop also killed the signaling SSE feed —
|
|
380
|
+
// re-establish it before dialing.
|
|
381
|
+
await this._deps.reconnectSignaling();
|
|
382
|
+
}
|
|
383
|
+
if (!this._redialing)
|
|
384
|
+
return;
|
|
385
|
+
await withTimeout(this._deps.dial(robotId), REDIAL_DIAL_TIMEOUT_MS, 'auto-reconnect dial timed out');
|
|
386
|
+
if (!this._redialing)
|
|
387
|
+
return;
|
|
388
|
+
this._redialing = false;
|
|
389
|
+
log.info(`auto-reconnect: recovered on attempt ${attempt}`);
|
|
390
|
+
this._deps.emit('sessionReconnected', { attempt });
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
catch (e) {
|
|
394
|
+
log.warn(`auto-reconnect attempt ${attempt}/${maxAttempts} failed:`, e);
|
|
395
|
+
// A failed attempt can leave a half-open pc (e.g. the dial
|
|
396
|
+
// timed out mid-ICE). Clean it so the next attempt starts
|
|
397
|
+
// from a blank slate; also frees the robot-side slot via
|
|
398
|
+
// endSession when one was allocated.
|
|
399
|
+
if (this._redialing)
|
|
400
|
+
this._deps.teardownForRedial();
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
if (!this._redialing)
|
|
404
|
+
return;
|
|
405
|
+
this._redialing = false;
|
|
406
|
+
const message = `Auto-reconnect gave up after ${maxAttempts} attempts (${cause})`;
|
|
407
|
+
log.warn(message);
|
|
408
|
+
this._deps.emit('sessionStopped', { reason: 'reconnect_failed', message });
|
|
409
|
+
this._emitError(new Error(message));
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Cancellable backoff sleep — `cancelRedial` wakes it immediately.
|
|
413
|
+
* A timer firing after a cancel is harmless: the loop re-checks
|
|
414
|
+
* `_redialing` right after every sleep.
|
|
415
|
+
*/
|
|
416
|
+
_redialSleep(ms) {
|
|
417
|
+
if (ms <= 0)
|
|
418
|
+
return Promise.resolve();
|
|
419
|
+
return new Promise((resolve) => {
|
|
420
|
+
this._redialWake = resolve;
|
|
421
|
+
setTimeout(() => {
|
|
422
|
+
if (this._redialWake === resolve)
|
|
423
|
+
this._redialWake = null;
|
|
424
|
+
resolve();
|
|
425
|
+
}, ms);
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Abort a pending re-dial. Called from stopSession(), disconnect()
|
|
430
|
+
* and an external startSession() — every path where the user (or the
|
|
431
|
+
* consumer app) takes over the session lifecycle.
|
|
432
|
+
*/
|
|
433
|
+
cancelRedial() {
|
|
434
|
+
if (!this._redialing && !this._redialWake)
|
|
435
|
+
return;
|
|
436
|
+
this._redialing = false;
|
|
437
|
+
if (this._redialWake) {
|
|
438
|
+
const wake = this._redialWake;
|
|
439
|
+
this._redialWake = null;
|
|
440
|
+
wake();
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
/* ─── Data-channel silence watchdog ─────────────────────────────── */
|
|
444
|
+
/** (Re)start the watchdog. Called when a session reaches ready. */
|
|
445
|
+
startDcWatchdog() {
|
|
446
|
+
this.stopDcWatchdog();
|
|
447
|
+
const now = Date.now();
|
|
448
|
+
this._lastDcInboundAt = now;
|
|
449
|
+
this._dcWatchdogLastTickAt = now;
|
|
450
|
+
this._dcSilenceNudged = false;
|
|
451
|
+
this._dcWatchdogId = setInterval(() => this._dcWatchdogTick(), DC_WATCHDOG_TICK_MS);
|
|
452
|
+
}
|
|
453
|
+
stopDcWatchdog() {
|
|
454
|
+
if (this._dcWatchdogId !== null) {
|
|
455
|
+
clearInterval(this._dcWatchdogId);
|
|
456
|
+
this._dcWatchdogId = null;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Liveness stamp: every inbound data-channel message — control
|
|
461
|
+
* replies, broadcasts, pose frames — proves the transport is alive.
|
|
462
|
+
*/
|
|
463
|
+
stampDcInbound() {
|
|
464
|
+
this._lastDcInboundAt = Date.now();
|
|
465
|
+
this._dcSilenceNudged = false;
|
|
466
|
+
}
|
|
467
|
+
_dcWatchdogTick() {
|
|
468
|
+
const now = Date.now();
|
|
469
|
+
const tickGap = now - this._dcWatchdogLastTickAt;
|
|
470
|
+
this._dcWatchdogLastTickAt = now;
|
|
471
|
+
if (!this._deps.isStreaming())
|
|
472
|
+
return;
|
|
473
|
+
// A tick that arrives way late means the timer was throttled or
|
|
474
|
+
// suspended (hidden tab, iOS app switch). Whatever silence we
|
|
475
|
+
// measure against that stale baseline is meaningless — and the
|
|
476
|
+
// background-transport question is already owned by the
|
|
477
|
+
// visibility-deferred ICE grace. Re-baseline and judge again
|
|
478
|
+
// from the next healthy tick.
|
|
479
|
+
if (tickGap > DC_WATCHDOG_TICK_MS * 3) {
|
|
480
|
+
this._lastDcInboundAt = now;
|
|
481
|
+
this._dcSilenceNudged = false;
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
if (typeof document !== 'undefined' && document.hidden) {
|
|
485
|
+
this._lastDcInboundAt = now;
|
|
486
|
+
this._dcSilenceNudged = false;
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
const silence = now - this._lastDcInboundAt;
|
|
490
|
+
if (silence < DC_SILENCE_NUDGE_MS)
|
|
491
|
+
return;
|
|
492
|
+
if (!this._dcSilenceNudged) {
|
|
493
|
+
// One extra get_state so a legitimately quiet channel (pose
|
|
494
|
+
// stream just died, poll on hold) gets a chance to answer
|
|
495
|
+
// before we call the transport dead. Any inbound message
|
|
496
|
+
// clears the flag via `stampDcInbound`.
|
|
497
|
+
this._dcSilenceNudged = true;
|
|
498
|
+
this._deps.nudgeState();
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
if (silence < DC_SILENCE_FATAL_MS)
|
|
502
|
+
return;
|
|
503
|
+
// Dead transport, and no redial to hand it to: emit once and
|
|
504
|
+
// stand down. The next startDcWatchdog() re-arms.
|
|
505
|
+
this._dcSilenceNudged = false;
|
|
506
|
+
const cause = `No data-channel traffic for ${silence}ms`;
|
|
507
|
+
if (this.maybeBeginRedial(cause))
|
|
508
|
+
return;
|
|
509
|
+
this.stopDcWatchdog();
|
|
510
|
+
this._emitError(new Error(cause));
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
/** Reject `p` with `label` if it hasn't settled within `ms`. */
|
|
514
|
+
function withTimeout(p, ms, label) {
|
|
515
|
+
return Promise.race([
|
|
516
|
+
p,
|
|
517
|
+
new Promise((_, rj) => setTimeout(() => rj(new Error(label)), ms)),
|
|
518
|
+
]);
|
|
519
|
+
}
|
|
520
|
+
//# sourceMappingURL=session-supervisor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-supervisor.js","sourceRoot":"","sources":["../../lib/session-supervisor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-scoped persistence of the HF OAuth token.
|
|
3
|
+
*
|
|
4
|
+
* The token lives in `sessionStorage` (tab-scoped, dies with the tab),
|
|
5
|
+
* under keys shared with the host shell and the embed bootstrap. On top
|
|
6
|
+
* of the OAuth expiry, reads enforce a SLIDING IDLE WINDOW: a token
|
|
7
|
+
* whose last successful read is older than `TOKEN_MAX_IDLE_MS` is
|
|
8
|
+
* treated as absent and wiped. That property replaced the host's old
|
|
9
|
+
* "wipe on pagehide" hygiene, which fired on every reload / navigation
|
|
10
|
+
* (pagehide can't tell a refresh from a tab close) and forced a
|
|
11
|
+
* re-login on every F5. The idle window keeps the one attack the wipe
|
|
12
|
+
* actually covered - a tab resurrected days later via session restore
|
|
13
|
+
* gets its sessionStorage back - without punishing reloads.
|
|
14
|
+
*
|
|
15
|
+
* `consumeOAuthErrorParams` supports the silent sign-in flow
|
|
16
|
+
* (`login({ prompt: 'none' })`): a failed silent authorization comes
|
|
17
|
+
* back as `?error=login_required...` query params, which must be
|
|
18
|
+
* stripped from the URL before the app router or a copy-pasted link
|
|
19
|
+
* leaks them around.
|
|
20
|
+
*/
|
|
21
|
+
/** Idle cutoff: a cached token unused for longer than this is dropped. */
|
|
22
|
+
export declare const TOKEN_MAX_IDLE_MS: number;
|
|
23
|
+
export interface StoredToken {
|
|
24
|
+
token: string;
|
|
25
|
+
username: string;
|
|
26
|
+
/** ISO date string of the OAuth expiry. */
|
|
27
|
+
expires: string;
|
|
28
|
+
}
|
|
29
|
+
/** Persist a token bundle and stamp its last-seen time. */
|
|
30
|
+
export declare function writeStoredToken(bundle: StoredToken): void;
|
|
31
|
+
/**
|
|
32
|
+
* Read the cached token if it is still usable, refreshing its
|
|
33
|
+
* last-seen stamp (sliding window). Returns `null` - and wipes the
|
|
34
|
+
* store - when the token is expired or idle for too long.
|
|
35
|
+
*
|
|
36
|
+
* A missing last-seen stamp (bundle written by an older SDK, or seeded
|
|
37
|
+
* by the embed's fragment-credentials path) is treated as fresh and
|
|
38
|
+
* stamped now, so a rollout never logs anyone out.
|
|
39
|
+
*/
|
|
40
|
+
export declare function readUsableToken(now?: number): StoredToken | null;
|
|
41
|
+
/** Remove every token key from the store. */
|
|
42
|
+
export declare function clearStoredToken(): void;
|
|
43
|
+
/**
|
|
44
|
+
* Detect an OAuth error return (`?error=...` from a `prompt=none`
|
|
45
|
+
* silent authorization), strip the OAuth params from the URL, and
|
|
46
|
+
* return the error code. Returns `null` when the URL carries no OAuth
|
|
47
|
+
* error. Other query params are preserved.
|
|
48
|
+
*/
|
|
49
|
+
export declare function consumeOAuthErrorParams(): string | null;
|
|
50
|
+
//# sourceMappingURL=token-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-store.d.ts","sourceRoot":"","sources":["../../lib/token-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAOH,0EAA0E;AAC1E,eAAO,MAAM,iBAAiB,QAAsB,CAAC;AAErD,MAAM,WAAW,WAAW;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,2DAA2D;AAC3D,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAS1D;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAmB,GAAG,WAAW,GAAG,IAAI,CAyB5E;AAED,6CAA6C;AAC7C,wBAAgB,gBAAgB,IAAI,IAAI,CASvC;AAeD;;;;;GAKG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,GAAG,IAAI,CAavD"}
|