@pollen-robotics/reachy-mini-sdk 1.8.0-main.3d52f7d → 1.8.0-main.54c2dcd
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/reachy-mini.d.ts +0 -55
- package/dist/lib/reachy-mini.d.ts.map +1 -1
- package/dist/lib/reachy-mini.js +9 -268
- package/dist/lib/reachy-mini.js.map +1 -1
- package/dist/lib/types.d.ts +0 -25
- package/dist/lib/types.d.ts.map +1 -1
- package/host/dist/chunks/{reachy-mini-CzgACSf_.js → reachy-mini-BV9nAnC1.js} +292 -433
- package/host/dist/entry/auto.js +1 -1
- package/host/dist/entry/embed.js +1 -1
- package/host/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -44,12 +44,6 @@ export declare class ReachyMini extends EventTarget implements ReachyMiniInstanc
|
|
|
44
44
|
private _sessionReject;
|
|
45
45
|
private _iceConnected;
|
|
46
46
|
private _dcOpen;
|
|
47
|
-
private _iceGraceTimer;
|
|
48
|
-
private _iceGraceReason;
|
|
49
|
-
private _pendingVisibilityHandler;
|
|
50
|
-
private _onlineHandler;
|
|
51
|
-
private _offlineHandler;
|
|
52
|
-
private _connectionChangeHandler;
|
|
53
47
|
private readonly _pendingMotionCompletions;
|
|
54
48
|
private _videoElement;
|
|
55
49
|
constructor(options?: ReachyMiniOptions);
|
|
@@ -81,55 +75,6 @@ export declare class ReachyMini extends EventTarget implements ReachyMiniInstanc
|
|
|
81
75
|
private _failSessionRejected;
|
|
82
76
|
stopSession(): Promise<void>;
|
|
83
77
|
disconnect(): void;
|
|
84
|
-
/**
|
|
85
|
-
* Cancel any pending ICE grace timer and visibility handler. Called
|
|
86
|
-
* on a healed `connected`/`completed` transition AND from the
|
|
87
|
-
* lifecycle teardown paths so a callback can't fire after `_pc`
|
|
88
|
-
* is closed.
|
|
89
|
-
*/
|
|
90
|
-
private _clearIceGrace;
|
|
91
|
-
/**
|
|
92
|
-
* Start a grace window. After `ms`, re-check the live ICE state:
|
|
93
|
-
* - If we healed back to `connected`/`completed`, the timer was
|
|
94
|
-
* already cancelled in `oniceconnectionstatechange`, so we
|
|
95
|
-
* never get here.
|
|
96
|
-
* - If we're still in the originally-observed bad state (or
|
|
97
|
-
* worse), surface the error and reject any pending session
|
|
98
|
-
* promise. The original code path is preserved verbatim so
|
|
99
|
-
* downstream consumers see the same `error` payload shape.
|
|
100
|
-
*/
|
|
101
|
-
private _scheduleIceGrace;
|
|
102
|
-
/**
|
|
103
|
-
* `disconnected` while the tab is hidden. JS timers are throttled
|
|
104
|
-
* in background tabs (Chrome clamps to ~1 Hz, Safari can pause
|
|
105
|
-
* altogether), so a foreground grace timer would either miss the
|
|
106
|
-
* window or fire long after the connection healed. Wait for the
|
|
107
|
-
* tab to come back, then re-evaluate.
|
|
108
|
-
*/
|
|
109
|
-
private _armIceGraceOnVisibility;
|
|
110
|
-
/**
|
|
111
|
-
* Install browser-level network listeners and forward them as
|
|
112
|
-
* public `networkOnline` / `networkOffline` / `networkChange`
|
|
113
|
-
* events on this instance. Idempotent: called from
|
|
114
|
-
* `startSession()`, removed by `_uninstallNetworkListeners` on
|
|
115
|
-
* teardown. Reachable only when there's a live `window`
|
|
116
|
-
* (defensive guard for SSR / test environments).
|
|
117
|
-
*
|
|
118
|
-
* `online` / `offline` are semantically about CONNECTIVITY:
|
|
119
|
-
* "does the OS think we can reach the internet". They flip
|
|
120
|
-
* symmetrically.
|
|
121
|
-
*
|
|
122
|
-
* `connection.change` (NetworkInformation API, Chrome / Android
|
|
123
|
-
* WebView only) is semantically about the TRANSPORT: it fires
|
|
124
|
-
* on Wi-Fi → 4G swaps, AP roams, etc. without necessarily going
|
|
125
|
-
* through `offline`. We forward it as its own `networkChange`
|
|
126
|
-
* event rather than aliasing it onto `networkOnline`, so
|
|
127
|
-
* consumers don't have to guess whether they're seeing a real
|
|
128
|
-
* connectivity recovery or a silent transport swap.
|
|
129
|
-
*/
|
|
130
|
-
private _installNetworkListeners;
|
|
131
|
-
/** Counterpart to `_installNetworkListeners`. */
|
|
132
|
-
private _uninstallNetworkListeners;
|
|
133
78
|
/**
|
|
134
79
|
* Atomic raw-units pose update over the data channel. Channels you
|
|
135
80
|
* omit are held at their last commanded value (per-axis, independent).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reachy-mini.d.ts","sourceRoot":"","sources":["../../lib/reachy-mini.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAuBH,OAAO,KAAK,EACR,uBAAuB,EACvB,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,EAEjB,kBAAkB,EAClB,QAAQ,EACR,eAAe,EACf,cAAc,EACd,wBAAwB,EAExB,kBAAkB,EAClB,iBAAiB,EACjB,SAAS,EACT,UAAU,EAEV,oBAAoB,EACpB,kBAAkB,EACrB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"reachy-mini.d.ts","sourceRoot":"","sources":["../../lib/reachy-mini.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/reachy-mini.js
CHANGED
|
@@ -7,39 +7,6 @@ import { oauthHandleRedirectIfPresent, oauthLoginUrl, } from '@huggingface/hub';
|
|
|
7
7
|
import { degToRad, rpyToMatrix } from './math.js';
|
|
8
8
|
import { consumeFragmentCredentials, readPreselectedRobotIdFromUrl, sdpHasAudioSendRecv, } from './url-helpers.js';
|
|
9
9
|
import { UPLOAD_CHUNK_SIZE, UPLOAD_BUFFERED_HIGH_WATER, UPLOAD_BUFFERED_LOW_WATER, hasCompressionStream, makeUploadId, bytesToBase64, gzipBase64, clampVolume, } from './upload-helpers.js';
|
|
10
|
-
// ─── Internal constants ──────────────────────────────────────────────────────
|
|
11
|
-
/**
|
|
12
|
-
* How long we tolerate `iceConnectionState === 'disconnected'` before
|
|
13
|
-
* surfacing it as an error. The spec defines this state as transient
|
|
14
|
-
* (browsers keep STUN keep-alives running and usually heal in 1-2 s
|
|
15
|
-
* on WiFi blips, AP roams, brief 4G dropouts). Consumers watching
|
|
16
|
-
* `iceStateChange` directly should outlive this window before
|
|
17
|
-
* showing any fatal UI.
|
|
18
|
-
*/
|
|
19
|
-
const ICE_DISCONNECT_GRACE_MS = 3000;
|
|
20
|
-
/**
|
|
21
|
-
* Grace before treating `iceConnectionState === 'failed'` as terminal.
|
|
22
|
-
* The spec says `failed` IS terminal, but we've observed real
|
|
23
|
-
* `failed → connected` flips on rapid AP roams and iOS BT route
|
|
24
|
-
* changes — 1 s of debounce absorbs those without noticeably
|
|
25
|
-
* delaying a real failure.
|
|
26
|
-
*/
|
|
27
|
-
const ICE_FAILED_GRACE_MS = 1000;
|
|
28
|
-
/**
|
|
29
|
-
* Ceiling on how long we'll keep `_armIceGraceOnVisibility` waiting
|
|
30
|
-
* for the tab to come back. The daemon's `webrtcsink` runs a STUN
|
|
31
|
-
* consent-freshness check (RFC 7675, ~30 s default) and unilaterally
|
|
32
|
-
* tears its side of the session down past that window, releasing the
|
|
33
|
-
* producer slot on central. If the user backgrounded the tab for
|
|
34
|
-
* longer than this, running another 3 s foreground grace is a lie —
|
|
35
|
-
* the underlying transport is gone, nothing can recover. Give up
|
|
36
|
-
* straight away so the host shows the real "session expired" UX
|
|
37
|
-
* instead of a fake "Reconnecting…" badge that's never going to
|
|
38
|
-
* heal. 60 s gives a 2× margin over the daemon-side timeout — long
|
|
39
|
-
* enough to absorb a "phone in pocket for 45 s" case, short enough
|
|
40
|
-
* to be honest with the user.
|
|
41
|
-
*/
|
|
42
|
-
const MAX_VISIBILITY_DEFER_MS = 60_000;
|
|
43
10
|
export class ReachyMini extends EventTarget {
|
|
44
11
|
// ─── Config ──────────────────────────────────────────────────────────
|
|
45
12
|
_signalingUrl;
|
|
@@ -95,19 +62,6 @@ export class ReachyMini extends EventTarget {
|
|
|
95
62
|
_sessionReject = null;
|
|
96
63
|
_iceConnected = false;
|
|
97
64
|
_dcOpen = false;
|
|
98
|
-
// ─── Resilience: ICE-blip debounce + network awareness ──────────────
|
|
99
|
-
// Backs `_scheduleIceGrace` / `_armIceGraceOnVisibility` and the
|
|
100
|
-
// `networkOnline` / `networkOffline` / `networkChange` forwarders.
|
|
101
|
-
// All three handler slots are scoped to the lifetime of a live
|
|
102
|
-
// session (installed in `startSession`, cleared in
|
|
103
|
-
// `stopSession` / `disconnect` / `_handleEndSession` /
|
|
104
|
-
// `_failSessionRejected`).
|
|
105
|
-
_iceGraceTimer = null;
|
|
106
|
-
_iceGraceReason = null;
|
|
107
|
-
_pendingVisibilityHandler = null;
|
|
108
|
-
_onlineHandler = null;
|
|
109
|
-
_offlineHandler = null;
|
|
110
|
-
_connectionChangeHandler = null;
|
|
111
65
|
// ─── Motion completion plumbing (wake_up / goto_sleep) ───────────────
|
|
112
66
|
_pendingMotionCompletions = {
|
|
113
67
|
wake_up: [],
|
|
@@ -464,9 +418,6 @@ export class ReachyMini extends EventTarget {
|
|
|
464
418
|
this._pc = new RTCPeerConnection({
|
|
465
419
|
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
|
|
466
420
|
});
|
|
467
|
-
// Scope `networkOnline` / `networkOffline` / `networkChange`
|
|
468
|
-
// event forwarding to the lifetime of this session.
|
|
469
|
-
this._installNetworkListeners();
|
|
470
421
|
return new Promise((resolve, reject) => {
|
|
471
422
|
this._sessionResolve = resolve;
|
|
472
423
|
this._sessionReject = reject;
|
|
@@ -503,38 +454,21 @@ export class ReachyMini extends EventTarget {
|
|
|
503
454
|
const s = this._pc?.iceConnectionState;
|
|
504
455
|
if (!s)
|
|
505
456
|
return;
|
|
506
|
-
// Public, granular event: every transition is visible to
|
|
507
|
-
// consumers so they can render finer UX (e.g. a transient
|
|
508
|
-
// "Reconnecting…" badge during `disconnected`) without
|
|
509
|
-
// having to attach their own handler to `_pc`.
|
|
510
|
-
this._emit('iceStateChange', { state: s });
|
|
511
457
|
if (s === 'connected' || s === 'completed') {
|
|
512
|
-
// Healed — cancel any pending grace from a previous blip.
|
|
513
|
-
this._clearIceGrace();
|
|
514
458
|
this._iceConnected = true;
|
|
515
459
|
this._checkSessionReady();
|
|
516
|
-
return;
|
|
517
460
|
}
|
|
518
|
-
if (s === '
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
this._armIceGraceOnVisibility();
|
|
461
|
+
else if (s === 'failed') {
|
|
462
|
+
const err = new Error('ICE connection failed');
|
|
463
|
+
if (this._sessionReject) {
|
|
464
|
+
this._sessionReject(err);
|
|
465
|
+
this._sessionResolve = null;
|
|
466
|
+
this._sessionReject = null;
|
|
525
467
|
}
|
|
526
|
-
|
|
527
|
-
this._scheduleIceGrace(ICE_DISCONNECT_GRACE_MS, 'disconnected');
|
|
528
|
-
}
|
|
529
|
-
return;
|
|
468
|
+
this._emit('error', { source: 'webrtc', error: err });
|
|
530
469
|
}
|
|
531
|
-
if (s === '
|
|
532
|
-
|
|
533
|
-
// `failed → connected` on rapid AP roams / BT route
|
|
534
|
-
// changes on iOS. Give the ICE agent a short window
|
|
535
|
-
// to surprise us before rejecting the session.
|
|
536
|
-
this._scheduleIceGrace(ICE_FAILED_GRACE_MS, 'failed');
|
|
537
|
-
return;
|
|
470
|
+
else if (s === 'disconnected') {
|
|
471
|
+
this._emit('error', { source: 'webrtc', error: new Error('ICE disconnected') });
|
|
538
472
|
}
|
|
539
473
|
};
|
|
540
474
|
this._pc.ondatachannel = (e) => {
|
|
@@ -561,10 +495,6 @@ export class ReachyMini extends EventTarget {
|
|
|
561
495
|
: `Session rejected: ${msg.reason || 'unknown reason'}`);
|
|
562
496
|
err.reason = msg.reason ?? null;
|
|
563
497
|
err.activeApp = msg.activeApp ?? null;
|
|
564
|
-
// Resilience teardown BEFORE closing `_pc` so a queued grace
|
|
565
|
-
// callback can't dereference a dead handle.
|
|
566
|
-
this._clearIceGrace();
|
|
567
|
-
this._uninstallNetworkListeners();
|
|
568
498
|
if (this._pc) {
|
|
569
499
|
this._pc.close();
|
|
570
500
|
this._pc = null;
|
|
@@ -612,10 +542,6 @@ export class ReachyMini extends EventTarget {
|
|
|
612
542
|
}
|
|
613
543
|
this._logSubscribers.clear();
|
|
614
544
|
this._rejectPendingMotionCompletions(new Error('Session stopped'));
|
|
615
|
-
// Tear down resilience plumbing BEFORE closing `_pc` so a
|
|
616
|
-
// queued grace callback can't dereference a dead handle.
|
|
617
|
-
this._clearIceGrace();
|
|
618
|
-
this._uninstallNetworkListeners();
|
|
619
545
|
if (this._sessionReject) {
|
|
620
546
|
this._sessionReject(new Error('Session stopped'));
|
|
621
547
|
this._sessionResolve = null;
|
|
@@ -686,9 +612,6 @@ export class ReachyMini extends EventTarget {
|
|
|
686
612
|
}
|
|
687
613
|
this._logSubscribers.clear();
|
|
688
614
|
this._rejectPendingMotionCompletions(new Error('Disconnected'));
|
|
689
|
-
// Mirrors the resilience teardown in `stopSession()`.
|
|
690
|
-
this._clearIceGrace();
|
|
691
|
-
this._uninstallNetworkListeners();
|
|
692
615
|
if (this._sessionReject) {
|
|
693
616
|
this._sessionReject(new Error('Disconnected'));
|
|
694
617
|
this._sessionResolve = null;
|
|
@@ -726,185 +649,6 @@ export class ReachyMini extends EventTarget {
|
|
|
726
649
|
this._state = 'disconnected';
|
|
727
650
|
this._emit('disconnected', { reason: 'user' });
|
|
728
651
|
}
|
|
729
|
-
// ─── Resilience: ICE-blip debounce + network awareness ───────────────
|
|
730
|
-
//
|
|
731
|
-
// Both halves below are intentionally generic (they don't know about
|
|
732
|
-
// motion, audio, or the FSM): they just smooth out browser-level
|
|
733
|
-
// events so the consumer's own state machine doesn't get torn down
|
|
734
|
-
// by routine WiFi/4G/screen-off noise.
|
|
735
|
-
/**
|
|
736
|
-
* Cancel any pending ICE grace timer and visibility handler. Called
|
|
737
|
-
* on a healed `connected`/`completed` transition AND from the
|
|
738
|
-
* lifecycle teardown paths so a callback can't fire after `_pc`
|
|
739
|
-
* is closed.
|
|
740
|
-
*/
|
|
741
|
-
_clearIceGrace() {
|
|
742
|
-
if (this._iceGraceTimer !== null) {
|
|
743
|
-
clearTimeout(this._iceGraceTimer);
|
|
744
|
-
this._iceGraceTimer = null;
|
|
745
|
-
}
|
|
746
|
-
this._iceGraceReason = null;
|
|
747
|
-
if (this._pendingVisibilityHandler && typeof document !== 'undefined') {
|
|
748
|
-
document.removeEventListener('visibilitychange', this._pendingVisibilityHandler);
|
|
749
|
-
}
|
|
750
|
-
this._pendingVisibilityHandler = null;
|
|
751
|
-
}
|
|
752
|
-
/**
|
|
753
|
-
* Start a grace window. After `ms`, re-check the live ICE state:
|
|
754
|
-
* - If we healed back to `connected`/`completed`, the timer was
|
|
755
|
-
* already cancelled in `oniceconnectionstatechange`, so we
|
|
756
|
-
* never get here.
|
|
757
|
-
* - If we're still in the originally-observed bad state (or
|
|
758
|
-
* worse), surface the error and reject any pending session
|
|
759
|
-
* promise. The original code path is preserved verbatim so
|
|
760
|
-
* downstream consumers see the same `error` payload shape.
|
|
761
|
-
*/
|
|
762
|
-
_scheduleIceGrace(ms, reason) {
|
|
763
|
-
// Coalesce: if a grace is already pending and the reason hasn't
|
|
764
|
-
// changed, keep the original timer so a flurry of identical
|
|
765
|
-
// transitions doesn't reset the clock. If the reason changed
|
|
766
|
-
// (typically `disconnected` → `failed`, but also the reverse on
|
|
767
|
-
// some Android WebViews), replace the timer with the new
|
|
768
|
-
// (reason, ms) pair — the latest signal wins.
|
|
769
|
-
if (this._iceGraceTimer !== null) {
|
|
770
|
-
if (this._iceGraceReason === reason)
|
|
771
|
-
return;
|
|
772
|
-
clearTimeout(this._iceGraceTimer);
|
|
773
|
-
}
|
|
774
|
-
this._iceGraceReason = reason;
|
|
775
|
-
this._iceGraceTimer = setTimeout(() => {
|
|
776
|
-
this._iceGraceTimer = null;
|
|
777
|
-
const r = this._iceGraceReason;
|
|
778
|
-
this._iceGraceReason = null;
|
|
779
|
-
const s = this._pc?.iceConnectionState;
|
|
780
|
-
if (s === 'connected' || s === 'completed')
|
|
781
|
-
return; // healed
|
|
782
|
-
if (r === 'disconnected' && s === 'disconnected') {
|
|
783
|
-
this._emit('error', {
|
|
784
|
-
source: 'webrtc',
|
|
785
|
-
error: new Error(`ICE stuck in 'disconnected' for > ${ms}ms`),
|
|
786
|
-
});
|
|
787
|
-
return;
|
|
788
|
-
}
|
|
789
|
-
if (r === 'failed' || s === 'failed') {
|
|
790
|
-
const err = new Error('ICE connection failed');
|
|
791
|
-
if (this._sessionReject) {
|
|
792
|
-
this._sessionReject(err);
|
|
793
|
-
this._sessionResolve = null;
|
|
794
|
-
this._sessionReject = null;
|
|
795
|
-
}
|
|
796
|
-
this._emit('error', { source: 'webrtc', error: err });
|
|
797
|
-
}
|
|
798
|
-
}, ms);
|
|
799
|
-
}
|
|
800
|
-
/**
|
|
801
|
-
* `disconnected` while the tab is hidden. JS timers are throttled
|
|
802
|
-
* in background tabs (Chrome clamps to ~1 Hz, Safari can pause
|
|
803
|
-
* altogether), so a foreground grace timer would either miss the
|
|
804
|
-
* window or fire long after the connection healed. Wait for the
|
|
805
|
-
* tab to come back, then re-evaluate.
|
|
806
|
-
*/
|
|
807
|
-
_armIceGraceOnVisibility() {
|
|
808
|
-
if (this._pendingVisibilityHandler)
|
|
809
|
-
return;
|
|
810
|
-
const deferredAt = Date.now();
|
|
811
|
-
const handler = () => {
|
|
812
|
-
if (typeof document !== 'undefined' && document.hidden)
|
|
813
|
-
return;
|
|
814
|
-
document.removeEventListener('visibilitychange', handler);
|
|
815
|
-
this._pendingVisibilityHandler = null;
|
|
816
|
-
if (!this._pc)
|
|
817
|
-
return;
|
|
818
|
-
const s = this._pc.iceConnectionState;
|
|
819
|
-
if (s === 'connected' || s === 'completed')
|
|
820
|
-
return; // healed in bg
|
|
821
|
-
// Ceiling: if the user backgrounded past the daemon's
|
|
822
|
-
// ICE-consent freshness window the session is gone from
|
|
823
|
-
// the daemon's side regardless of what `_pc` reports
|
|
824
|
-
// locally. Running another foreground grace would tell
|
|
825
|
-
// the user "Reconnecting…" for a recovery that can never
|
|
826
|
-
// happen. Escalate immediately so the host renders the
|
|
827
|
-
// real "session expired" UX. See MAX_VISIBILITY_DEFER_MS.
|
|
828
|
-
if (Date.now() - deferredAt > MAX_VISIBILITY_DEFER_MS) {
|
|
829
|
-
const err = new Error('Session expired while tab was backgrounded');
|
|
830
|
-
if (this._sessionReject) {
|
|
831
|
-
this._sessionReject(err);
|
|
832
|
-
this._sessionResolve = null;
|
|
833
|
-
this._sessionReject = null;
|
|
834
|
-
}
|
|
835
|
-
this._emit('error', { source: 'webrtc', error: err });
|
|
836
|
-
return;
|
|
837
|
-
}
|
|
838
|
-
if (s === 'failed') {
|
|
839
|
-
this._scheduleIceGrace(ICE_FAILED_GRACE_MS, 'failed');
|
|
840
|
-
return;
|
|
841
|
-
}
|
|
842
|
-
// Still disconnected when we came back — give it a normal
|
|
843
|
-
// foreground grace window now that timers fire reliably.
|
|
844
|
-
this._scheduleIceGrace(ICE_DISCONNECT_GRACE_MS, 'disconnected');
|
|
845
|
-
};
|
|
846
|
-
document.addEventListener('visibilitychange', handler);
|
|
847
|
-
this._pendingVisibilityHandler = handler;
|
|
848
|
-
}
|
|
849
|
-
/**
|
|
850
|
-
* Install browser-level network listeners and forward them as
|
|
851
|
-
* public `networkOnline` / `networkOffline` / `networkChange`
|
|
852
|
-
* events on this instance. Idempotent: called from
|
|
853
|
-
* `startSession()`, removed by `_uninstallNetworkListeners` on
|
|
854
|
-
* teardown. Reachable only when there's a live `window`
|
|
855
|
-
* (defensive guard for SSR / test environments).
|
|
856
|
-
*
|
|
857
|
-
* `online` / `offline` are semantically about CONNECTIVITY:
|
|
858
|
-
* "does the OS think we can reach the internet". They flip
|
|
859
|
-
* symmetrically.
|
|
860
|
-
*
|
|
861
|
-
* `connection.change` (NetworkInformation API, Chrome / Android
|
|
862
|
-
* WebView only) is semantically about the TRANSPORT: it fires
|
|
863
|
-
* on Wi-Fi → 4G swaps, AP roams, etc. without necessarily going
|
|
864
|
-
* through `offline`. We forward it as its own `networkChange`
|
|
865
|
-
* event rather than aliasing it onto `networkOnline`, so
|
|
866
|
-
* consumers don't have to guess whether they're seeing a real
|
|
867
|
-
* connectivity recovery or a silent transport swap.
|
|
868
|
-
*/
|
|
869
|
-
_installNetworkListeners() {
|
|
870
|
-
if (this._onlineHandler || typeof window === 'undefined')
|
|
871
|
-
return;
|
|
872
|
-
const onOnline = () => this._emit('networkOnline', {});
|
|
873
|
-
const onOffline = () => this._emit('networkOffline', {});
|
|
874
|
-
window.addEventListener('online', onOnline);
|
|
875
|
-
window.addEventListener('offline', onOffline);
|
|
876
|
-
this._onlineHandler = onOnline;
|
|
877
|
-
this._offlineHandler = onOffline;
|
|
878
|
-
const conn = navigator.connection;
|
|
879
|
-
if (conn && typeof conn.addEventListener === 'function') {
|
|
880
|
-
const onChange = () => this._emit('networkChange', {
|
|
881
|
-
effectiveType: conn.effectiveType,
|
|
882
|
-
downlink: conn.downlink,
|
|
883
|
-
rtt: conn.rtt,
|
|
884
|
-
saveData: conn.saveData,
|
|
885
|
-
});
|
|
886
|
-
conn.addEventListener('change', onChange);
|
|
887
|
-
this._connectionChangeHandler = onChange;
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
/** Counterpart to `_installNetworkListeners`. */
|
|
891
|
-
_uninstallNetworkListeners() {
|
|
892
|
-
if (typeof window !== 'undefined') {
|
|
893
|
-
if (this._onlineHandler) {
|
|
894
|
-
window.removeEventListener('online', this._onlineHandler);
|
|
895
|
-
}
|
|
896
|
-
if (this._offlineHandler) {
|
|
897
|
-
window.removeEventListener('offline', this._offlineHandler);
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
const conn = navigator.connection;
|
|
901
|
-
if (conn && this._connectionChangeHandler && typeof conn.removeEventListener === 'function') {
|
|
902
|
-
conn.removeEventListener('change', this._connectionChangeHandler);
|
|
903
|
-
}
|
|
904
|
-
this._onlineHandler = null;
|
|
905
|
-
this._offlineHandler = null;
|
|
906
|
-
this._connectionChangeHandler = null;
|
|
907
|
-
}
|
|
908
652
|
// ─── Commands ────────────────────────────────────────────────────────
|
|
909
653
|
/**
|
|
910
654
|
* Atomic raw-units pose update over the data channel. Channels you
|
|
@@ -1531,9 +1275,6 @@ export class ReachyMini extends EventTarget {
|
|
|
1531
1275
|
const err = new Error(friendly || `Session ended before it could start: ${reason || 'unknown reason'}`);
|
|
1532
1276
|
err.reason = reason ?? null;
|
|
1533
1277
|
this._emit('sessionRejected', { reason, activeApp: null });
|
|
1534
|
-
// Resilience teardown alongside the PC close path.
|
|
1535
|
-
this._clearIceGrace();
|
|
1536
|
-
this._uninstallNetworkListeners();
|
|
1537
1278
|
if (this._pc) {
|
|
1538
1279
|
this._pc.close();
|
|
1539
1280
|
this._pc = null;
|